@svadmin/ui 0.38.2 → 0.38.7

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 +8 -4
  2. package/dist/app.theme.css +8 -4
  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 +185 -159
  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 -14
  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 +14 -11
package/dist/app.css CHANGED
@@ -1,5 +1,9 @@
1
1
  /* Typography — Inter font for clean, modern look */
2
2
 
3
+ /* Tailwind v4 resolves this path relative to the published CSS file, so both
4
+ * src/app.css in the workspace and dist/app*.css on npm scan real components. */
5
+ @source "./components";
6
+
3
7
  /* @svadmin/ui — Design Tokens (clean admin shell)
4
8
  * Auto-imported by AdminApp. Can be overridden by consumer's CSS.
5
9
  *
@@ -8,12 +12,12 @@
8
12
  */
9
13
 
10
14
  @layer base {
11
- /* Global border-color reset — ensures Tailwind border utilities
12
- * (border-r, border-t, etc.) use the theme color instead of currentColor */
15
+ /* Use the semantic token so consumers can normalize legacy channel-only
16
+ * values without the base reset falling back to currentColor. */
13
17
  *,
14
18
  ::after,
15
19
  ::before {
16
- border-color: var(--border);
20
+ border-color: var(--color-border, var(--border));
17
21
  }
18
22
 
19
23
  :root {
@@ -382,7 +386,7 @@ button:active:not(:disabled) {
382
386
  animation: svadmin-page-enter 0.25s ease-out;
383
387
  }
384
388
 
385
- /* ⑥ Toast slide-in refinement (sonner-svelte) — tighter timing */
389
+ /* ⑥ Toast slide-in refinement (svelte-sonner) — tighter timing */
386
390
  [data-sonner-toaster] [data-sonner-toast] {
387
391
  transition-duration: 200ms !important;
388
392
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1) !important;
@@ -1,5 +1,9 @@
1
1
  /* Typography — Inter font for clean, modern look */
2
2
 
3
+ /* Tailwind v4 resolves this path relative to the published CSS file, so both
4
+ * src/app.css in the workspace and dist/app*.css on npm scan real components. */
5
+ @source "./components";
6
+
3
7
  /* @svadmin/ui — Design Tokens (clean admin shell)
4
8
  * Auto-imported by AdminApp. Can be overridden by consumer's CSS.
5
9
  *
@@ -8,12 +12,12 @@
8
12
  */
9
13
 
10
14
  @layer base {
11
- /* Global border-color reset — ensures Tailwind border utilities
12
- * (border-r, border-t, etc.) use the theme color instead of currentColor */
15
+ /* Use the semantic token so consumers can normalize legacy channel-only
16
+ * values without the base reset falling back to currentColor. */
13
17
  *,
14
18
  ::after,
15
19
  ::before {
16
- border-color: var(--border);
20
+ border-color: var(--color-border, var(--border));
17
21
  }
18
22
 
19
23
  :root {
@@ -382,7 +386,7 @@ button:active:not(:disabled) {
382
386
  animation: svadmin-page-enter 0.25s ease-out;
383
387
  }
384
388
 
385
- /* ⑥ Toast slide-in refinement (sonner-svelte) — tighter timing */
389
+ /* ⑥ Toast slide-in refinement (svelte-sonner) — tighter timing */
386
390
  [data-sonner-toaster] [data-sonner-toast] {
387
391
  transition-duration: 200ms !important;
388
392
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1) !important;
@@ -1,14 +1,15 @@
1
1
  <script lang="ts">
2
- import { getResources, getChatProvider } from '@svadmin/core';
3
- import { navigate } from '@svadmin/core/router';
4
- import { t } from '@svadmin/core/i18n';
5
- import { toggleTheme } from '@svadmin/core';
6
- import { Command } from 'cmdk-sv';
2
+ import { captureAdminContext, getResources, getChatProvider, toggleTheme } from '@svadmin/core';
3
+ import { useTranslation } from '@svadmin/core/i18n';
4
+ import { Command } from './ui/command/index.js';
7
5
  import * as Dialog from './ui/dialog/index.js';
8
6
  import { Search, LayoutDashboard, Plus, Sun, FileText, Sparkles, Loader2 } from '@lucide/svelte';
9
7
  import MarkdownRenderer from './MarkdownRenderer.svelte';
10
8
 
9
+ const i18n = useTranslation();
10
+
11
11
  let { open = $bindable(false) } = $props<{ open?: boolean }>();
12
+ const adminContext = captureAdminContext();
12
13
  let searchValue = $state('');
13
14
 
14
15
  const resources = getResources();
@@ -80,14 +81,14 @@
80
81
  }
81
82
  }
82
83
 
83
- const itemClass = 'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50';
84
+ const itemClass = 'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[selected]:bg-accent data-[selected]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50';
84
85
  </script>
85
86
 
86
87
  <Dialog.Dialog bind:open onOpenChange={(v: boolean) => !v && close()}>
87
88
  <Dialog.DialogContent class="overflow-hidden p-0 sm:max-w-[600px] border-none shadow-2xl">
88
89
  <Command.Root
89
90
  shouldFilter={!aiMode}
90
- class="flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground [&_[data-cmdk-group-heading]]:text-muted-foreground [&_[data-cmdk-group-heading]]:px-2 [&_[data-cmdk-group-heading]]:py-1.5 [&_[data-cmdk-group-heading]]:text-xs [&_[data-cmdk-group-heading]]:font-semibold [&_[data-cmdk-group-heading]]:uppercase [&_[data-cmdk-group-heading]]:tracking-wider"
91
+ class="flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground [&_[data-command-group-heading]]:text-muted-foreground [&_[data-command-group-heading]]:px-2 [&_[data-command-group-heading]]:py-1.5 [&_[data-command-group-heading]]:text-xs [&_[data-command-group-heading]]:font-semibold [&_[data-command-group-heading]]:uppercase [&_[data-command-group-heading]]:tracking-wider"
91
92
  >
92
93
  <div class="flex items-center border-b px-3 h-12 relative" data-cmdk-input-wrapper="">
93
94
  {#if aiMode}
@@ -119,13 +120,13 @@
119
120
  </Command.Empty>
120
121
 
121
122
  <!-- Navigation -->
122
- <Command.Group heading={t('common.home')}>
123
- <Command.Item value="home" onSelect={() => act(() => navigate('/'))} class={itemClass}>
123
+ <Command.Group heading={i18n.t('common.home')}>
124
+ <Command.Item value="home" onSelect={() => act(() => adminContext.navigate('/'))} class={itemClass}>
124
125
  <LayoutDashboard class="h-4 w-4 text-muted-foreground" />
125
- {t('common.home')}
126
+ {i18n.t('common.home')}
126
127
  </Command.Item>
127
128
  {#each resources as r, _i (_i)}
128
- <Command.Item value={r.name} onSelect={() => act(() => navigate(`/${r.name}`))} class={itemClass}>
129
+ <Command.Item value={r.name} onSelect={() => act(() => adminContext.navigate(`/${r.name}`))} class={itemClass}>
129
130
  <FileText class="h-4 w-4 text-muted-foreground" />
130
131
  {r.label}
131
132
  </Command.Item>
@@ -135,16 +136,16 @@
135
136
  <Command.Separator class="mx-1 my-1 h-px bg-border" />
136
137
 
137
138
  <!-- Actions -->
138
- <Command.Group heading={t('common.actions')}>
139
+ <Command.Group heading={i18n.t('common.actions')}>
139
140
  {#each resources as r, _i (_i)}
140
- <Command.Item value={`create-${r.name}`} onSelect={() => act(() => navigate(`/${r.name}/create`))} class={itemClass}>
141
+ <Command.Item value={`create-${r.name}`} onSelect={() => act(() => adminContext.navigate(`/${r.name}/create`))} class={itemClass}>
141
142
  <Plus class="h-4 w-4 text-muted-foreground" />
142
- {t('common.create')} {r.label}
143
+ {i18n.t('common.create')} {r.label}
143
144
  </Command.Item>
144
145
  {/each}
145
146
  <Command.Item value="toggle-theme" onSelect={() => act(() => toggleTheme())} class={itemClass}>
146
147
  <Sun class="h-4 w-4 text-muted-foreground" />
147
- {t('common.toggleTheme')}
148
+ {i18n.t('common.toggleTheme')}
148
149
  </Command.Item>
149
150
  </Command.Group>
150
151
  </Command.List>
@@ -1,12 +1,13 @@
1
1
  <script lang="ts">
2
- import { t } from '@svadmin/core/i18n';
2
+ import { useTranslation } from '@svadmin/core/i18n';
3
3
  import { getResources, getDataProviderNames } from '@svadmin/core';
4
- import { getLocale } from '@svadmin/core/i18n';
5
4
  import { getResolvedTheme, getColorTheme } from '@svadmin/core';
6
5
  import * as Card from './ui/card/index.js';
7
6
  import { Badge } from './ui/badge/index.js';
8
7
  import { Info, Database, Globe, Palette, Layers } from '@lucide/svelte';
9
8
 
9
+ const i18n = useTranslation();
10
+
10
11
  const resources = getResources();
11
12
  const providerNames = getDataProviderNames();
12
13
 
@@ -15,7 +16,7 @@
15
16
 
16
17
  <div class="space-y-6">
17
18
  <div>
18
- <h2 class="text-xl font-semibold text-foreground">{t('settings.about')}</h2>
19
+ <h2 class="text-xl font-semibold text-foreground">{i18n.t('settings.about')}</h2>
19
20
  </div>
20
21
 
21
22
  <!-- Version -->
@@ -29,7 +30,7 @@
29
30
  <Card.CardContent>
30
31
  <div class="grid gap-4 text-sm">
31
32
  <div class="flex items-center justify-between">
32
- <span class="text-muted-foreground">{t('settings.version')}</span>
33
+ <span class="text-muted-foreground">{i18n.t('settings.version')}</span>
33
34
  <Badge variant="secondary" class="font-mono text-xs">{version}</Badge>
34
35
  </div>
35
36
  </div>
@@ -41,7 +42,7 @@
41
42
  <Card.CardHeader class="pb-3">
42
43
  <Card.CardTitle class="text-base flex items-center gap-2">
43
44
  <Layers class="h-4 w-4 text-muted-foreground" />
44
- {t('settings.registeredResources')}
45
+ {i18n.t('settings.registeredResources')}
45
46
  </Card.CardTitle>
46
47
  </Card.CardHeader>
47
48
  <Card.CardContent>
@@ -61,7 +62,7 @@
61
62
  <Card.CardHeader class="pb-3">
62
63
  <Card.CardTitle class="text-base flex items-center gap-2">
63
64
  <Database class="h-4 w-4 text-muted-foreground" />
64
- {t('settings.dataProvider')}
65
+ {i18n.t('settings.dataProvider')}
65
66
  </Card.CardTitle>
66
67
  </Card.CardHeader>
67
68
  <Card.CardContent>
@@ -78,25 +79,25 @@
78
79
  <Card.CardHeader class="pb-3">
79
80
  <Card.CardTitle class="text-base flex items-center gap-2">
80
81
  <Globe class="h-4 w-4 text-muted-foreground" />
81
- {t('settings.currentEnvironment')}
82
+ {i18n.t('settings.currentEnvironment')}
82
83
  </Card.CardTitle>
83
84
  </Card.CardHeader>
84
85
  <Card.CardContent>
85
86
  <div class="grid gap-3 text-sm">
86
87
  <div class="flex items-center justify-between">
87
- <span class="text-muted-foreground">{t('settings.language')}</span>
88
- <span class="font-mono text-xs">{getLocale()}</span>
88
+ <span class="text-muted-foreground">{i18n.t('settings.language')}</span>
89
+ <span class="font-mono text-xs">{i18n.locale}</span>
89
90
  </div>
90
91
  <div class="h-px bg-border"></div>
91
92
  <div class="flex items-center justify-between">
92
- <span class="text-muted-foreground">{t('settings.themeMode')}</span>
93
+ <span class="text-muted-foreground">{i18n.t('settings.themeMode')}</span>
93
94
  <span class="font-mono text-xs">{getResolvedTheme()}</span>
94
95
  </div>
95
96
  <div class="h-px bg-border"></div>
96
97
  <div class="flex items-center justify-between">
97
98
  <span class="text-muted-foreground flex items-center gap-1.5">
98
99
  <Palette class="h-3.5 w-3.5" />
99
- {t('settings.colorAccent')}
100
+ {i18n.t('settings.colorAccent')}
100
101
  </span>
101
102
  <span class="font-mono text-xs">{getColorTheme()}</span>
102
103
  </div>
@@ -1,11 +1,35 @@
1
+ <script module lang="ts">
2
+ const loadPublicProfilePage = () => import('./profile/PublicProfilePage.svelte');
3
+ const loadGetStartedPage = () => import('./account/GetStartedPage.svelte');
4
+ const loadCompanyProfilePage = () => import('./account/CompanyProfilePage.svelte');
5
+ const loadSettingsPlainPage = () => import('./account/SettingsPlainPage.svelte');
6
+ const loadSettingsSidebarPage = () => import('./account/SettingsSidebarPage.svelte');
7
+ const loadSettingsEnterprisePage = () => import('./account/SettingsEnterprisePage.svelte');
8
+ const loadImportMembersPage = () => import('./account/ImportMembersPage.svelte');
9
+ const loadMembersStarterPage = () => import('./account/MembersStarterPage.svelte');
10
+ const loadTeamMembersPage = () => import('./account/TeamMembersPage.svelte');
11
+ const loadSecurityLogPage = () => import('./account/SecurityLogPage.svelte');
12
+ const loadUserCardsNFTPage = () => import('./network/UserCardsNFTPage.svelte');
13
+ const loadTeamCrewTablePage = () => import('./network/TeamCrewTablePage.svelte');
14
+ const loadTwoFactorAuthPage = () => import('./TwoFactorAuthPage.svelte');
15
+ </script>
16
+
1
17
  <script lang="ts">
2
18
  // Auto-inject design tokens so consumers don't need to manually import
3
19
  import '../app.css';
4
- import type { Component, Snippet } from 'svelte';
5
- import type { DataProvider, AuthProvider, ResourceDefinition, ThemeMode, RouterProvider, ThemeConfig, MenuItem, TaskProvider } from '@svadmin/core';
6
- import { setDataProvider, setAuthProvider, setResources, setLocale, setTheme, setRouterProvider, setTaskProvider, createHashRouterProvider, configureTheme } from '@svadmin/core';
7
- import { t } from '@svadmin/core/i18n';
8
- import { navigate } from '@svadmin/core/router';
20
+ import { onDestroy, onMount, untrack, type Component, type Snippet } from 'svelte';
21
+ import type { DataProvider, AuthProvider, ResourceDefinition, ThemeMode, RouterProvider, ThemeConfig, MenuItem, TaskProvider, I18nProvider } from '@svadmin/core';
22
+ import {
23
+ captureAdminContext,
24
+ createHashRouterProvider,
25
+ createI18nScope,
26
+ provideAdminContext,
27
+ provideI18nScope,
28
+ registerThemeOwner,
29
+ unregisterThemeOwner,
30
+ updateThemeOwner,
31
+ } from '@svadmin/core';
32
+ import { useTranslation } from '@svadmin/core/i18n';
9
33
  import { QueryClient, QueryClientProvider, type DefaultOptions } from '@tanstack/svelte-query';
10
34
  import { setComponentRegistry, type ComponentRegistry } from '../component-registry.svelte.js';
11
35
  import Layout from './Layout.svelte';
@@ -24,24 +48,12 @@
24
48
  import TaskQueueDrawer from './TaskQueueDrawer.svelte';
25
49
  import SettingsPage from './SettingsPage.svelte';
26
50
  import ErrorPage from './ErrorPage.svelte';
27
- import PublicProfilePage from './profile/PublicProfilePage.svelte';
28
- import GetStartedPage from './account/GetStartedPage.svelte';
29
- import CompanyProfilePage from './account/CompanyProfilePage.svelte';
30
- import SettingsPlainPage from './account/SettingsPlainPage.svelte';
31
- import SettingsSidebarPage from './account/SettingsSidebarPage.svelte';
32
- import SettingsEnterprisePage from './account/SettingsEnterprisePage.svelte';
33
- import ImportMembersPage from './account/ImportMembersPage.svelte';
34
- import MembersStarterPage from './account/MembersStarterPage.svelte';
35
- import TeamMembersPage from './account/TeamMembersPage.svelte';
36
- import SecurityLogPage from './account/SecurityLogPage.svelte';
37
- import UserCardsNFTPage from './network/UserCardsNFTPage.svelte';
38
- import TeamCrewTablePage from './network/TeamCrewTablePage.svelte';
39
- import TwoFactorAuthPage from './TwoFactorAuthPage.svelte';
51
+ import LazyPage from './LazyPage.svelte';
40
52
  import { Button } from './ui/button/index.js';
41
53
  import { Input } from './ui/input/index.js';
42
54
  import { Badge } from './ui/badge/index.js';
43
55
  import { Skeleton } from './ui/skeleton/index.js';
44
- import { initRouter, getRoute, getParams } from '../router-state.svelte.js';
56
+ import { provideRouterState } from '../router-state.svelte.js';
45
57
 
46
58
  interface Props {
47
59
  dataProvider: DataProvider;
@@ -49,7 +61,10 @@
49
61
  taskProvider?: TaskProvider;
50
62
  routerProvider?: RouterProvider;
51
63
  resources: ResourceDefinition[];
64
+ /** Bindable tree-local locale; defaults to the provider locale or per-tree browser detection (English during SSR). */
52
65
  locale?: string;
66
+ /** Optional tree-local translation provider. */
67
+ i18nProvider?: I18nProvider;
53
68
  title?: string;
54
69
  defaultTheme?: ThemeMode;
55
70
  /** Theme configuration for dark-first mode, CSS overrides, etc. */
@@ -90,7 +105,8 @@
90
105
  taskProvider,
91
106
  routerProvider,
92
107
  resources,
93
- locale,
108
+ locale = $bindable(),
109
+ i18nProvider,
94
110
  title = 'Admin',
95
111
  defaultTheme,
96
112
  themeConfig: userThemeConfig,
@@ -122,28 +138,89 @@
122
138
  const resolvedRouter = $derived(routerProvider ?? createHashRouterProvider());
123
139
  const resolvedRouteMode = $derived(routeMode ?? (routerProvider ? 'auto' : 'hash'));
124
140
 
141
+ const routerState = provideRouterState(untrack(() => resolvedRouter));
142
+ const scopedRouterProvider: RouterProvider = {
143
+ go(options) {
144
+ resolvedRouter.go(options);
145
+ routerState.sync();
146
+ },
147
+ back() {
148
+ resolvedRouter.back();
149
+ routerState.sync();
150
+ },
151
+ parse() {
152
+ return resolvedRouter.parse();
153
+ },
154
+ formatLink(path) {
155
+ return resolvedRouter.formatLink?.(path) ?? `#${path.replace(/^#/, '')}`;
156
+ },
157
+ };
158
+
159
+ $effect.pre(() => routerState.init(resolvedRouter));
160
+ onDestroy(() => routerState.destroy());
161
+
162
+ function updateBoundLocale(nextLocale: string) {
163
+ locale = nextLocale;
164
+ }
165
+
166
+ const i18nScope = createI18nScope({
167
+ locale: untrack(() => locale),
168
+ provider: untrack(() => i18nProvider),
169
+ onLocaleChange: updateBoundLocale,
170
+ });
171
+ provideI18nScope(i18nScope);
172
+ const translation = useTranslation();
173
+
174
+ $effect.pre(() => {
175
+ const ownerProvider = i18nProvider;
176
+ const ownerLocale = locale;
177
+ untrack(() => i18nScope.updateOwner({
178
+ locale: ownerLocale,
179
+ provider: ownerProvider,
180
+ onLocaleChange: updateBoundLocale,
181
+ }));
182
+ });
183
+
184
+ let themeOwner = $state.raw<ReturnType<typeof registerThemeOwner>>(undefined);
185
+
186
+ onMount(() => {
187
+ themeOwner = registerThemeOwner({
188
+ defaultTheme,
189
+ themeConfig: userThemeConfig,
190
+ });
191
+
192
+ return () => {
193
+ unregisterThemeOwner(themeOwner);
194
+ themeOwner = undefined;
195
+ };
196
+ });
197
+
198
+ $effect(() => {
199
+ const owner = themeOwner;
200
+ const ownerOptions = {
201
+ defaultTheme,
202
+ themeConfig: userThemeConfig,
203
+ };
204
+ untrack(() => updateThemeOwner(owner, ownerOptions));
205
+ });
206
+
125
207
  function syncComponentRegistry() {
126
208
  setComponentRegistry(mergedComponents);
127
209
  }
128
210
 
129
- function syncAppContext() {
130
- setDataProvider(dataProvider);
131
- if (authProvider) setAuthProvider(authProvider);
132
- if (taskProvider) setTaskProvider(taskProvider);
133
- setResources(resources);
134
- setRouterProvider(resolvedRouter);
135
- if (locale) setLocale(locale);
136
- if (userThemeConfig) configureTheme(userThemeConfig);
137
- if (defaultTheme) setTheme(defaultTheme);
138
- }
211
+ provideAdminContext({
212
+ get dataProvider() { return dataProvider; },
213
+ get authProvider() { return authProvider ?? null; },
214
+ get taskProvider() { return taskProvider; },
215
+ get resources() { return resources; },
216
+ get routerProvider() { return scopedRouterProvider; },
217
+ });
218
+ const adminContext = captureAdminContext();
139
219
 
140
- // Set up initial context synchronously so children can access resources immediately during first render.
220
+ // Context must be available synchronously to descendants during their first render.
141
221
  syncComponentRegistry();
142
- syncAppContext();
143
222
 
144
- // Set up context — use $effect so prop changes are tracked
145
223
  $effect.pre(syncComponentRegistry);
146
- $effect.pre(syncAppContext);
147
224
 
148
225
  const queryClient = $derived(providedQueryClient ?? new QueryClient({
149
226
  defaultOptions: {
@@ -152,24 +229,20 @@
152
229
  },
153
230
  }));
154
231
 
155
- function syncRouter() {
156
- initRouter(resolvedRouter);
157
- }
158
-
159
- // Initialize router with provider synchronously
160
- syncRouter();
161
- $effect.pre(syncRouter);
162
-
163
232
  // Reactive getters for route state
164
- const route = $derived(getRoute());
165
- const params = $derived(getParams());
233
+ const route = $derived(routerState.route);
234
+ const params = $derived(routerState.params);
166
235
  const resourceNames = $derived(new Set(resources.map((resource) => resource.name)));
167
236
  const hasRouteResource = $derived(!params.resource || resourceNames.has(params.resource));
168
237
  const currentResourcePages = $derived(params.resource ? resourcePages?.[params.resource] : undefined);
169
238
 
170
239
  // Auth check
171
- let isAuthenticated = $state(false);
172
- let authChecked = $state(false);
240
+ let isAuthenticated = $state(untrack(() => !authProvider));
241
+ let authChecked = $state(untrack(() => !authProvider));
242
+
243
+ async function navigateWithinApp(path: string) {
244
+ await adminContext.navigate(path);
245
+ }
173
246
 
174
247
  $effect(() => {
175
248
  let cancelled = false;
@@ -183,12 +256,13 @@
183
256
  }
184
257
  return;
185
258
  }
259
+ authChecked = false;
186
260
  authProvider.check().then(result => {
187
261
  if (cancelled) return;
188
262
  isAuthenticated = result.authenticated;
189
263
  authChecked = true;
190
264
  if (!result.authenticated && _route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
191
- navigate(result.redirectTo ?? '/login');
265
+ void navigateWithinApp(result.redirectTo ?? '/login');
192
266
  }
193
267
  }).catch(err => {
194
268
  if (cancelled) return;
@@ -196,7 +270,7 @@
196
270
  isAuthenticated = false;
197
271
  authChecked = true;
198
272
  if (_route !== '/login' && _route !== '/register' && _route !== '/forgot-password' && _route !== '/update-password') {
199
- navigate('/login');
273
+ void navigateWithinApp('/login');
200
274
  }
201
275
  });
202
276
 
@@ -220,7 +294,7 @@
220
294
  defaultIdentifier={loginDefaults?.identifier}
221
295
  defaultPassword={loginDefaults?.password}
222
296
  loginHint={loginDefaults?.hint}
223
- onSuccess={() => { isAuthenticated = true; navigate('/'); }}
297
+ onSuccess={() => { isAuthenticated = true; void navigateWithinApp('/'); }}
224
298
  />
225
299
  {:else if route === '/register' && authProvider?.register}
226
300
  <RegisterPage {title} />
@@ -229,49 +303,49 @@
229
303
  {:else if route === '/update-password' && authProvider?.updatePassword}
230
304
  <UpdatePasswordPage {title} />
231
305
  {:else if route === '/login' || route === '/register' || route === '/forgot-password' || route === '/update-password'}
232
- <ConfigErrorScreen title="{title} — {t('common.configRequired')}" />
306
+ <ConfigErrorScreen title="{title} — {translation.t('common.configRequired')}" />
233
307
  {:else if isAuthenticated || !authProvider}
234
308
  <Layout {title} {menu} {siteUrl} routeMode={resolvedRouteMode}>
235
309
  {#key route + (params.resource ?? '') + (params.id ?? '') + (params.variant ?? '') + (params.columns ?? '')}
236
310
  <div class="svadmin-page-enter">
237
311
  {#if route === '/2fa' || route === '/authentication/branded/2fa'}
238
- <TwoFactorAuthPage />
312
+ <LazyPage loader={loadTwoFactorAuthPage} props={{}} />
239
313
  {:else if route === '/public-profile'}
240
- <PublicProfilePage variant="default" initialTab="projects" />
314
+ <LazyPage loader={loadPublicProfilePage} props={{ variant: 'default', initialTab: 'projects' }} />
241
315
  {:else if route === '/public-profile/projects/:columns'}
242
- <PublicProfilePage variant="default" initialTab="projects" columns={params.columns?.includes('3') ? 3 : 2} />
316
+ <LazyPage loader={loadPublicProfilePage} props={{ variant: 'default', initialTab: 'projects', columns: params.columns?.includes('3') ? 3 : 2 }} />
243
317
  {:else if route === '/public-profile/activity'}
244
- <PublicProfilePage variant="default" initialTab="activity" />
318
+ <LazyPage loader={loadPublicProfilePage} props={{ variant: 'default', initialTab: 'activity' }} />
245
319
  {:else if route === '/public-profile/teams'}
246
- <PublicProfilePage variant="default" initialTab="teams" />
320
+ <LazyPage loader={loadPublicProfilePage} props={{ variant: 'default', initialTab: 'teams' }} />
247
321
  {:else if route === '/public-profile/profiles/:variant'}
248
- <PublicProfilePage variant={(params.variant === 'company' || params.variant === 'gamer' || params.variant === 'default' ? params.variant : 'default') as 'company' | 'gamer' | 'default'} />
322
+ <LazyPage loader={loadPublicProfilePage} props={{ variant: (params.variant === 'company' || params.variant === 'gamer' || params.variant === 'default' ? params.variant : 'default') as 'company' | 'gamer' | 'default' }} />
249
323
  {:else if route === '/account/get-started' || route === '/account/home/get-started'}
250
- <GetStartedPage />
324
+ <LazyPage loader={loadGetStartedPage} props={{}} />
251
325
  {:else if route === '/account/home/user-profile'}
252
326
  <SettingsPage />
253
327
  {:else if route === '/account/company-profile' || route === '/account/home/company-profile'}
254
- <CompanyProfilePage />
328
+ <LazyPage loader={loadCompanyProfilePage} props={{}} />
255
329
  {:else if route === '/account/settings-plain' || route === '/account/home/settings-plain'}
256
- <SettingsPlainPage />
330
+ <LazyPage loader={loadSettingsPlainPage} props={{}} />
257
331
  {:else if route === '/account/settings-sidebar' || route === '/account/home/settings-sidebar'}
258
- <SettingsSidebarPage />
332
+ <LazyPage loader={loadSettingsSidebarPage} props={{}} />
259
333
  {:else if route === '/account/settings-enterprise' || route === '/account/home/settings-enterprise'}
260
- <SettingsEnterprisePage />
334
+ <LazyPage loader={loadSettingsEnterprisePage} props={{}} />
261
335
  {:else if route === '/account/:tab'}
262
336
  <SettingsPage />
263
337
  {:else if route === '/account/import-members' || route === '/account/members/import-members'}
264
- <ImportMembersPage />
338
+ <LazyPage loader={loadImportMembersPage} props={{}} />
265
339
  {:else if route === '/account/members-starter' || route === '/account/members/members-starter'}
266
- <MembersStarterPage />
340
+ <LazyPage loader={loadMembersStarterPage} props={{}} />
267
341
  {:else if route === '/account/team-members' || route === '/account/members/team-members'}
268
- <TeamMembersPage />
342
+ <LazyPage loader={loadTeamMembersPage} props={{}} />
269
343
  {:else if route === '/account/security-log' || route === '/account/security/security-log'}
270
- <SecurityLogPage />
344
+ <LazyPage loader={loadSecurityLogPage} props={{}} />
271
345
  {:else if route === '/network/user-cards' || route === '/network/user-cards/nft'}
272
- <UserCardsNFTPage />
346
+ <LazyPage loader={loadUserCardsNFTPage} props={{}} />
273
347
  {:else if route === '/network/team-crew' || route === '/network/user-table/team-crew'}
274
- <TeamCrewTablePage />
348
+ <LazyPage loader={loadTeamCrewTablePage} props={{}} />
275
349
  {:else if route.startsWith('/settings')}
276
350
  <SettingsPage />
277
351
  {:else if route === '/500' || (route === '/:resource' && params.resource === '500')}
@@ -282,8 +356,8 @@
282
356
  {@render dashboard()}
283
357
  {:else}
284
358
  <div class="space-y-4">
285
- <h1 class="text-xl font-semibold text-foreground">{t('common.welcome', { title })}</h1>
286
- <p class="text-muted-foreground">{t('common.dashboardHint')}</p>
359
+ <h1 class="text-xl font-semibold text-foreground">{translation.t('common.welcome', { title })}</h1>
360
+ <p class="text-muted-foreground">{translation.t('common.dashboardHint')}</p>
287
361
  </div>
288
362
  {/if}
289
363
  {:else if (route === '/:resource' || route === '/:parent/:parentId/:resource') && hasRouteResource}
@@ -320,7 +394,7 @@
320
394
  </Layout>
321
395
  {:else}
322
396
  <div class="flex h-screen items-center justify-center">
323
- <p class="text-muted-foreground">{t('common.redirecting')}</p>
397
+ <p class="text-muted-foreground">{translation.t('common.redirecting')}</p>
324
398
  </div>
325
399
  {/if}
326
400
  <Toast />
@@ -1,6 +1,6 @@
1
1
  import '../app.css';
2
- import type { Component, Snippet } from 'svelte';
3
- import type { DataProvider, AuthProvider, ResourceDefinition, ThemeMode, RouterProvider, ThemeConfig, MenuItem, TaskProvider } from '@svadmin/core';
2
+ import { type Component, type Snippet } from 'svelte';
3
+ import type { DataProvider, AuthProvider, ResourceDefinition, ThemeMode, RouterProvider, ThemeConfig, MenuItem, TaskProvider, I18nProvider } from '@svadmin/core';
4
4
  import { QueryClient, type DefaultOptions } from '@tanstack/svelte-query';
5
5
  import { type ComponentRegistry } from '../component-registry.svelte.js';
6
6
  interface Props {
@@ -9,7 +9,10 @@ interface Props {
9
9
  taskProvider?: TaskProvider;
10
10
  routerProvider?: RouterProvider;
11
11
  resources: ResourceDefinition[];
12
+ /** Bindable tree-local locale; defaults to the provider locale or per-tree browser detection (English during SSR). */
12
13
  locale?: string;
14
+ /** Optional tree-local translation provider. */
15
+ i18nProvider?: I18nProvider;
13
16
  title?: string;
14
17
  defaultTheme?: ThemeMode;
15
18
  /** Theme configuration for dark-first mode, CSS overrides, etc. */
@@ -59,6 +62,6 @@ interface Props {
59
62
  /** Optional TanStack Query default options override when QueryClient is not supplied. */
60
63
  queryClientDefaultOptions?: DefaultOptions;
61
64
  }
62
- declare const AdminApp: Component<Props, {}, "">;
65
+ declare const AdminApp: Component<Props, {}, "locale">;
63
66
  type AdminApp = ReturnType<typeof AdminApp>;
64
67
  export default AdminApp;