@svadmin/ui 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  import { Label } from './ui/label/index.js';
8
8
  import * as Alert from './ui/alert/index.js';
9
9
  import SvadminLogo from './SvadminLogo.svelte';
10
- import { User, ArrowLeft, CheckCircle, Loader2, AlertCircle } from '@lucide/svelte';
10
+ import { User, ArrowLeft, Loader2, AlertCircle } from '@lucide/svelte';
11
11
 
12
12
  const i18n = useTranslation();
13
13
 
@@ -16,7 +16,7 @@
16
16
  }>();
17
17
 
18
18
  const adminContext = captureAdminContext();
19
- const forgot = useForgotPassword();
19
+ const forgot = useForgotPassword({ successNotification: false });
20
20
 
21
21
  let identifier = $state('');
22
22
  let error = $state('');
@@ -82,14 +82,14 @@
82
82
  <div class="space-y-1 mb-8">
83
83
  <h1 class="text-2xl font-semibold tracking-tight">
84
84
  {#if sent}
85
- {i18n.t('auth.resetLinkSent')}
85
+ {i18n.t('auth.resetLinkSentTitle')}
86
86
  {:else}
87
87
  {i18n.t('auth.forgotPassword')}
88
88
  {/if}
89
89
  </h1>
90
90
  <p class="text-sm text-muted-foreground">
91
91
  {#if sent}
92
- {i18n.t('auth.resetLinkSent')}
92
+ {i18n.t('auth.resetLinkSentDescription')}
93
93
  {:else}
94
94
  {i18n.t('auth.forgotPasswordDescription')}
95
95
  {/if}
@@ -98,10 +98,6 @@
98
98
 
99
99
  {#if sent}
100
100
  <div class="space-y-4">
101
- <div class="flex items-center gap-3 rounded-lg border bg-muted/50 p-4">
102
- <CheckCircle class="h-5 w-5 text-primary shrink-0" />
103
- <p class="text-sm text-muted-foreground">{i18n.t('auth.resetLinkSent')}</p>
104
- </div>
105
101
  <Button variant="outline" class="w-full" onclick={() => adminContext.navigate('/login')}>
106
102
  <ArrowLeft class="h-4 w-4 mr-2" />
107
103
  {i18n.t('auth.backToLogin')}
@@ -35,7 +35,7 @@
35
35
  }>();
36
36
 
37
37
  const adminContext = captureAdminContext();
38
- const login = useLogin({ errorMessage: false });
38
+ const login = useLogin({ successNotification: false, errorMessage: false });
39
39
  const authProvider = $derived(adminContext.authProvider);
40
40
 
41
41
  function getInitialIdentifier() {
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  captureAdminContext,
4
4
  useGetIdentity,
5
+ useNotification,
5
6
  useUpdatePassword,
6
7
  type AuthProvider,
7
8
  } from '@svadmin/core';
@@ -12,12 +13,13 @@
12
13
  import * as Card from './ui/card/index.js';
13
14
  import * as Alert from './ui/alert/index.js';
14
15
  import PasswordInput from './PasswordInput.svelte';
15
- import { User, Mail, Lock, Loader2, AlertCircle, CheckCircle, Camera } from '@lucide/svelte';
16
+ import { User, Mail, Lock, Loader2, AlertCircle, Camera } from '@lucide/svelte';
16
17
 
17
18
  const i18n = useTranslation();
18
19
  const adminContext = captureAdminContext();
19
20
 
20
21
  const identity = useGetIdentity();
22
+ const notification = useNotification();
21
23
  const updatePw = useUpdatePassword();
22
24
 
23
25
  const authProvider = $derived(adminContext.authProvider);
@@ -27,7 +29,6 @@
27
29
  let editingProfile = $state(false);
28
30
  let editName = $state('');
29
31
  let profileSaving = $state(false);
30
- let profileSuccess = $state(false);
31
32
  let profileError = $state('');
32
33
  let avatarUploading = $state(false);
33
34
  let fileInput = $state<HTMLInputElement>();
@@ -35,21 +36,18 @@
35
36
  let newPassword = $state('');
36
37
  let confirmPassword = $state('');
37
38
  let pwError = $state('');
38
- let pwSuccess = $state(false);
39
39
  let scopeEpoch = 0;
40
40
 
41
41
  function resetProfileScope(): void {
42
42
  editingProfile = false;
43
43
  editName = '';
44
44
  profileSaving = false;
45
- profileSuccess = false;
46
45
  profileError = '';
47
46
  avatarUploading = false;
48
47
  currentPassword = '';
49
48
  newPassword = '';
50
49
  confirmPassword = '';
51
50
  pwError = '';
52
- pwSuccess = false;
53
51
  }
54
52
 
55
53
  function isActiveScope(
@@ -75,7 +73,6 @@
75
73
  function startEditProfile() {
76
74
  editName = identity.data?.name ?? '';
77
75
  editingProfile = true;
78
- profileSuccess = false;
79
76
  profileError = '';
80
77
  }
81
78
 
@@ -90,9 +87,9 @@
90
87
  const result = await provider.updateProfile({ name: editName });
91
88
  if (!isActiveScope(epoch, provider, scopedTenantIdentity)) return;
92
89
  if (result.success) {
93
- profileSuccess = true;
94
90
  editingProfile = false;
95
91
  identity.refetch();
92
+ notification.success(i18n.t('common.updateSuccess'), 3000);
96
93
  } else {
97
94
  profileError = result.error?.message ?? i18n.t('common.operationFailed');
98
95
  }
@@ -123,7 +120,6 @@
123
120
  async function handlePasswordChange(e: SubmitEvent) {
124
121
  e.preventDefault();
125
122
  pwError = '';
126
- pwSuccess = false;
127
123
 
128
124
  if (!newPassword) { pwError = i18n.t('auth.passwordRequired'); return; }
129
125
  if (newPassword !== confirmPassword) { pwError = i18n.t('auth.passwordMismatch'); return; }
@@ -138,7 +134,6 @@
138
134
  });
139
135
  if (!isActiveScope(epoch, provider, scopedTenantIdentity)) return;
140
136
  if (result.success) {
141
- pwSuccess = true;
142
137
  currentPassword = '';
143
138
  newPassword = '';
144
139
  confirmPassword = '';
@@ -174,13 +169,6 @@
174
169
  {i18n.t('common.loading')}
175
170
  </div>
176
171
  {:else if identity.data}
177
- {#if profileSuccess}
178
- <Alert.Root class="border-success/30 bg-success/5 text-success mb-4">
179
- <CheckCircle class="h-4 w-4" />
180
- <Alert.Description>{i18n.t('common.updateSuccess')}</Alert.Description>
181
- </Alert.Root>
182
- {/if}
183
-
184
172
  <div class="flex items-start gap-6">
185
173
  <!-- Avatar -->
186
174
  <div class="shrink-0 relative group">
@@ -292,13 +280,6 @@
292
280
  </Alert.Root>
293
281
  {/if}
294
282
 
295
- {#if pwSuccess}
296
- <Alert.Root class="border-success/30 bg-success/5 text-success">
297
- <CheckCircle class="h-4 w-4" />
298
- <Alert.Description>{i18n.t('profile.passwordChanged')}</Alert.Description>
299
- </Alert.Root>
300
- {/if}
301
-
302
283
  <PasswordInput
303
284
  id="current-password"
304
285
  label={i18n.t('profile.currentPassword')}
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
+ import { useNotification } from '@svadmin/core';
2
3
  import { useTranslation } from '@svadmin/core/i18n';
3
4
 
4
- import { AlertTriangle, CheckCircle2, KeyRound, Monitor, Shield, ShieldAlert, ShieldCheck, Smartphone, Trash2 } from '@lucide/svelte';
5
+ import { AlertTriangle, KeyRound, Monitor, Shield, ShieldAlert, ShieldCheck, Smartphone, Trash2 } from '@lucide/svelte';
5
6
  import type { Component } from 'svelte';
6
7
  import * as Alert from './ui/alert/index.js';
7
8
  import * as Card from './ui/card/index.js';
@@ -11,6 +12,7 @@
11
12
  import PasswordInput from './PasswordInput.svelte';
12
13
 
13
14
  const i18n = useTranslation();
15
+ const notification = useNotification();
14
16
 
15
17
  interface SessionInfo {
16
18
  id: string;
@@ -32,13 +34,11 @@
32
34
  let currentPassword = $state('');
33
35
  let newPassword = $state('');
34
36
  let confirmPassword = $state('');
35
- let passwordSuccessMessage = $state('');
36
37
  let passwordErrorMessage = $state('');
37
38
  let lastChanged = $state('3 months ago');
38
39
 
39
40
  function handlePasswordChange(event: SubmitEvent) {
40
41
  event.preventDefault();
41
- passwordSuccessMessage = '';
42
42
  passwordErrorMessage = '';
43
43
 
44
44
  if (!currentPassword) {
@@ -54,7 +54,7 @@
54
54
  return;
55
55
  }
56
56
 
57
- passwordSuccessMessage = i18n.t('profile.passwordChanged');
57
+ notification.success(i18n.t('profile.passwordChanged'), 3000);
58
58
  lastChanged = 'Just now';
59
59
  currentPassword = '';
60
60
  newPassword = '';
@@ -111,12 +111,6 @@
111
111
  </Card.CardHeader>
112
112
  <Card.CardContent>
113
113
  <form onsubmit={handlePasswordChange} class="max-w-sm space-y-4">
114
- {#if passwordSuccessMessage}
115
- <Alert.Root class="border-success/30 bg-success/5 text-success">
116
- <CheckCircle2 class="h-4 w-4" />
117
- <Alert.Description>{passwordSuccessMessage}</Alert.Description>
118
- </Alert.Root>
119
- {/if}
120
114
  {#if passwordErrorMessage}
121
115
  <Alert.Root variant="destructive">
122
116
  <AlertTriangle class="h-4 w-4" />
@@ -20,11 +20,12 @@
20
20
  const queue = getToastQueue();
21
21
  if (queue.length > 0) {
22
22
  for (const t of queue) {
23
+ const options = { duration: t.duration, id: t.key ?? t.id };
23
24
  switch (t.type) {
24
- case 'success': sonner.success(t.message, { duration: t.duration }); break;
25
- case 'error': sonner.error(t.message, { duration: t.duration }); break;
26
- case 'warning': sonner.warning(t.message, { duration: t.duration }); break;
27
- case 'info': sonner.info(t.message, { duration: t.duration }); break;
25
+ case 'success': sonner.success(t.message, options); break;
26
+ case 'error': sonner.error(t.message, options); break;
27
+ case 'warning': sonner.warning(t.message, options); break;
28
+ case 'info': sonner.info(t.message, options); break;
28
29
  }
29
30
  }
30
31
  consumeToastQueue();
@@ -46,7 +47,8 @@
46
47
  position: "top-right",
47
48
  richColors: true,
48
49
  closeButton: true,
49
- expand: true,
50
+ expand: false,
51
+ visibleToasts: 3,
50
52
  theme,
51
53
  toastOptions: {
52
54
  classes: {
@@ -0,0 +1,61 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { CircleAlert, Info, TriangleAlert } from '@lucide/svelte';
4
+ import { cn } from '../../utils.js';
5
+
6
+ export type FeedbackNoticeTone = 'info' | 'warning' | 'danger';
7
+ export type FeedbackNoticePriority = 'contextual' | 'blocking';
8
+
9
+ interface Props {
10
+ message: string;
11
+ tone?: FeedbackNoticeTone;
12
+ priority?: FeedbackNoticePriority;
13
+ action?: Snippet;
14
+ class?: string;
15
+ }
16
+
17
+ let {
18
+ message,
19
+ tone = 'info',
20
+ priority = 'contextual',
21
+ action,
22
+ class: className = '',
23
+ }: Props = $props();
24
+
25
+ const isBlocking = $derived(priority === 'blocking' || tone === 'danger');
26
+ const toneClass = $derived(
27
+ tone === 'danger'
28
+ ? 'border-destructive/40 bg-destructive/5 text-destructive'
29
+ : tone === 'warning'
30
+ ? 'border-warning/50 bg-warning/10 text-warning-foreground'
31
+ : 'border-border bg-muted/40 text-foreground',
32
+ );
33
+ </script>
34
+
35
+ <div
36
+ data-svadmin-feedback-notice
37
+ data-tone={tone}
38
+ data-priority={priority}
39
+ role={isBlocking ? 'alert' : 'status'}
40
+ aria-live={isBlocking ? 'assertive' : 'polite'}
41
+ class={cn(
42
+ 'flex w-full flex-col gap-3 rounded-md border px-3 py-2.5 text-sm sm:flex-row sm:items-center',
43
+ isBlocking && 'border-l-2',
44
+ toneClass,
45
+ className,
46
+ )}
47
+ >
48
+ <div class="flex min-w-0 flex-1 items-start gap-2.5">
49
+ {#if tone === 'danger'}
50
+ <CircleAlert class="mt-0.5 size-4 shrink-0" />
51
+ {:else if tone === 'warning'}
52
+ <TriangleAlert class="mt-0.5 size-4 shrink-0" />
53
+ {:else}
54
+ <Info class="mt-0.5 size-4 shrink-0 text-muted-foreground" />
55
+ {/if}
56
+ <p class="min-w-0 leading-5">{message}</p>
57
+ </div>
58
+ {#if action}
59
+ <div class="shrink-0">{@render action()}</div>
60
+ {/if}
61
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ export type FeedbackNoticeTone = 'info' | 'warning' | 'danger';
3
+ export type FeedbackNoticePriority = 'contextual' | 'blocking';
4
+ interface Props {
5
+ message: string;
6
+ tone?: FeedbackNoticeTone;
7
+ priority?: FeedbackNoticePriority;
8
+ action?: Snippet;
9
+ class?: string;
10
+ }
11
+ declare const FeedbackNotice: import("svelte").Component<Props, {}, "">;
12
+ type FeedbackNotice = ReturnType<typeof FeedbackNotice>;
13
+ export default FeedbackNotice;
@@ -30,3 +30,5 @@ export type { NetworkColumn } from './NetworkTable.types.js';
30
30
  export { default as OtpInput } from './OtpInput.svelte';
31
31
  export { default as TwoFactorStepper } from './TwoFactorStepper.svelte';
32
32
  export { default as SystemErrorState } from './SystemErrorState.svelte';
33
+ export { default as FeedbackNotice } from './FeedbackNotice.svelte';
34
+ export type { FeedbackNoticeTone, FeedbackNoticePriority } from './FeedbackNotice.svelte';
@@ -20,3 +20,4 @@ export { default as NetworkTable } from './NetworkTable.svelte';
20
20
  export { default as OtpInput } from './OtpInput.svelte';
21
21
  export { default as TwoFactorStepper } from './TwoFactorStepper.svelte';
22
22
  export { default as SystemErrorState } from './SystemErrorState.svelte';
23
+ export { default as FeedbackNotice } from './FeedbackNotice.svelte';
package/dist/index.d.ts CHANGED
@@ -63,6 +63,8 @@ export { default as InlineEdit } from "./components/InlineEdit.svelte";
63
63
  export { default as VirtualTable } from "./components/VirtualTable.svelte";
64
64
  export { default as DraggableHeader } from "./components/DraggableHeader.svelte";
65
65
  export { default as SettingsPage } from "./components/SettingsPage.svelte";
66
+ export { default as FeedbackNotice } from './components/content/FeedbackNotice.svelte';
67
+ export type { FeedbackNoticeTone, FeedbackNoticePriority, } from './components/content/FeedbackNotice.svelte';
66
68
  export { setRichTextEditor, getRichTextEditor } from "./editor-config.svelte.js";
67
69
  export { default as AppearanceSettings } from "./components/AppearanceSettings.svelte";
68
70
  export { default as AboutSettings } from "./components/AboutSettings.svelte";
package/dist/index.js CHANGED
@@ -66,6 +66,7 @@ export { default as InlineEdit } from "./components/InlineEdit.svelte";
66
66
  export { default as VirtualTable } from "./components/VirtualTable.svelte";
67
67
  export { default as DraggableHeader } from "./components/DraggableHeader.svelte";
68
68
  export { default as SettingsPage } from "./components/SettingsPage.svelte";
69
+ export { default as FeedbackNotice } from './components/content/FeedbackNotice.svelte';
69
70
  // Editor Registry
70
71
  export { setRichTextEditor, getRichTextEditor } from "./editor-config.svelte.js";
71
72
  // Settings & Preferences
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -39,7 +39,7 @@
39
39
  "./components/*": "./dist/components/*"
40
40
  },
41
41
  "peerDependencies": {
42
- "@svadmin/core": "^0.37.0",
42
+ "@svadmin/core": "^0.38.0",
43
43
  "@tanstack/svelte-query": "^6.1.38",
44
44
  "highlight.js": "^11.11.1",
45
45
  "isomorphic-dompurify": "^3.22.0",