@svadmin/ui 0.22.1 → 0.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.22.1",
3
+ "version": "0.23.1",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -22,15 +22,11 @@
22
22
  "peerDependencies": {
23
23
  "@svadmin/core": "^0.5.0",
24
24
  "@tanstack/svelte-query": "^6.0.0",
25
- "bits-ui": "^2.0.0",
26
- "clsx": "^2.0.0",
27
25
  "highlight.js": "^11.0.0",
28
26
  "isomorphic-dompurify": "^2.0.0",
29
27
  "marked": "^15.0.0",
30
28
  "marked-highlight": "^2.2.0",
31
- "svelte": "^5.0.0",
32
- "tailwind-merge": "^3.0.0",
33
- "tailwind-variants": "^3.0.0"
29
+ "svelte": "^5.0.0"
34
30
  },
35
31
  "peerDependenciesMeta": {
36
32
  "marked": {
@@ -52,10 +48,13 @@
52
48
  "dependencies": {
53
49
  "@lucide/svelte": "^0.577.0",
54
50
  "@tanstack/svelte-table": "^9.0.0-alpha.10",
51
+ "bits-ui": "^2.0.0",
52
+ "clsx": "^2.0.0",
55
53
  "cmdk-sv": "^0.0.19",
56
54
  "lucide-svelte": "^0.577.0",
57
55
  "sonner-svelte": "^0.3.3",
58
- "vaul-svelte": "^0.3.2"
56
+ "tailwind-merge": "^3.0.0",
57
+ "tailwind-variants": "^3.0.0"
59
58
  },
60
59
  "license": "MIT",
61
60
  "author": "zuohuadong",
@@ -308,9 +308,7 @@
308
308
  const ids = Object.keys(rowSelection);
309
309
  confirmMessage = t('common.batchDeleteConfirm', { count: ids.length });
310
310
  confirmAction = async () => {
311
- for (const id of ids) {
312
- await deleteMutation.mutateAsync({ id, resource: resourceName });
313
- }
311
+ await Promise.allSettled(ids.map(id => deleteMutation.mutateAsync({ id, resource: resourceName })));
314
312
  rowSelection = {};
315
313
  confirmOpen = false;
316
314
  };
@@ -78,7 +78,7 @@
78
78
  </script>
79
79
 
80
80
  <div class="space-y-1.5">
81
- <Label for={field.key}>
81
+ <Label for={field.key} id="label-{field.key}">
82
82
  {field.label}
83
83
  {#if field.required}
84
84
  <span class="text-destructive">*</span>
@@ -233,7 +233,11 @@
233
233
  {/if}
234
234
 
235
235
  {:else if field.type === 'multiselect'}
236
- <div class="space-y-2 rounded-lg border border-input p-3 max-h-48 overflow-y-auto">
236
+ <div
237
+ class="space-y-2 rounded-lg border border-input p-3 max-h-48 overflow-y-auto"
238
+ role="group"
239
+ aria-labelledby="label-{field.key}"
240
+ >
237
241
  {#each field.options ?? [] as opt}
238
242
  <label class="flex items-center gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-1 py-0.5 transition-colors">
239
243
  <Checkbox
@@ -17,16 +17,23 @@
17
17
  ChevronLeft, ChevronDown, LogOut, Sun, Moon, Palette
18
18
  } from 'lucide-svelte';
19
19
 
20
- let { collapsed, identity, title, onToggle, onLogout, menu, legacyHashRouting = false }: {
20
+ let { collapsed, identity, title, onToggle, onLogout, menu, routeMode = 'auto' }: {
21
21
  collapsed: boolean;
22
22
  identity: Identity | null;
23
23
  title: string;
24
24
  onToggle: () => void;
25
25
  onLogout: () => void;
26
26
  menu?: MenuItem[];
27
- legacyHashRouting?: boolean;
27
+ /** 'hash' for SPA hash routing, 'path' for SvelteKit/filesystem routing, 'auto' to detect */
28
+ routeMode?: 'hash' | 'path' | 'auto';
28
29
  } = $props();
29
30
 
31
+ const effectiveRouteMode = $derived(
32
+ routeMode === 'auto'
33
+ ? (typeof window !== 'undefined' && window.location.hash.startsWith('#/') ? 'hash' : 'path')
34
+ : routeMode
35
+ );
36
+
30
37
  const resources = getResources();
31
38
 
32
39
  const iconMap: Record<string, typeof LayoutDashboard> = {
@@ -98,7 +105,7 @@
98
105
 
99
106
  function isActive(itemPath: string): boolean {
100
107
  if (itemPath === '/') return path === '/';
101
- return path.startsWith(itemPath);
108
+ return path === itemPath || path.startsWith(itemPath + "/");
102
109
  }
103
110
 
104
111
  /** Prefetch resource data on hover for instant navigation */
@@ -206,7 +213,7 @@
206
213
  {#each group.items as item}
207
214
  {@const active = isActive(item.path)}
208
215
  <a
209
- href={legacyHashRouting ? `#${item.path}` : item.path}
216
+ href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
210
217
  class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
211
218
  {active
212
219
  ? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
@@ -229,7 +236,7 @@
229
236
  {#snippet child({ props }: { props: Record<string, unknown> })}
230
237
  <a
231
238
  {...props}
232
- href={legacyHashRouting ? `#${item.path}` : item.path}
239
+ href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
233
240
  class="flex items-center justify-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
234
241
  {active
235
242
  ? 'bg-sidebar-accent text-sidebar-accent-foreground'
@@ -245,7 +252,7 @@
245
252
  </Tooltip.Root>
246
253
  {:else}
247
254
  <a
248
- href={legacyHashRouting ? `#${item.path}` : item.path}
255
+ href={effectiveRouteMode === 'hash' ? `#${item.path}` : item.path}
249
256
  class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
250
257
  {active
251
258
  ? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
@@ -1,8 +1,33 @@
1
1
  <script lang="ts">
2
- import { Toaster } from 'sonner-svelte';
3
- import { getResolvedTheme } from '@svadmin/core';
2
+ import { Toaster, toast as sonner } from 'sonner-svelte';
3
+ import { getResolvedTheme, getToastQueue, consumeToastQueue, getPromiseQueue, consumePromiseQueue } from '@svadmin/core';
4
4
 
5
5
  const theme = $derived(getResolvedTheme() === 'dark' ? 'dark' : 'light');
6
+
7
+ $effect(() => {
8
+ const queue = getToastQueue();
9
+ if (queue.length > 0) {
10
+ for (const t of queue) {
11
+ switch (t.type) {
12
+ case 'success': sonner.success(t.message, { duration: t.duration }); break;
13
+ case 'error': sonner.error(t.message, { duration: t.duration }); break;
14
+ case 'warning': sonner.warning(t.message, { duration: t.duration }); break;
15
+ case 'info': sonner.info(t.message, { duration: t.duration }); break;
16
+ }
17
+ }
18
+ consumeToastQueue();
19
+ }
20
+ });
21
+
22
+ $effect(() => {
23
+ const pQueue = getPromiseQueue();
24
+ if (pQueue.length > 0) {
25
+ for (const p of pQueue) {
26
+ sonner.promise(p.promise, p.opts);
27
+ }
28
+ consumePromiseQueue();
29
+ }
30
+ });
6
31
  </script>
7
32
 
8
33
  <Toaster
@@ -1,21 +0,0 @@
1
- <script lang="ts">
2
- import { Drawer as DrawerPrimitive } from 'vaul-svelte';
3
- import { cn } from '../../../utils.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- let { class: className, children, ...restProps }: { class?: string; children?: Snippet; [key: string]: unknown } = $props();
7
- </script>
8
-
9
- <DrawerPrimitive.Portal>
10
- <DrawerPrimitive.Overlay class="fixed inset-0 z-50 bg-black/80" />
11
- <DrawerPrimitive.Content
12
- class={cn(
13
- "bg-background fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border",
14
- className
15
- )}
16
- {...restProps}
17
- >
18
- <div class="bg-muted mx-auto mt-4 h-2 w-[100px] rounded-full"></div>
19
- {@render children?.()}
20
- </DrawerPrimitive.Content>
21
- </DrawerPrimitive.Portal>
@@ -1,11 +0,0 @@
1
- <script lang="ts">
2
- import { Drawer as DrawerPrimitive } from 'vaul-svelte';
3
- import { cn } from '../../../utils.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- let { class: className, children, ...restProps }: { class?: string; children?: Snippet; [key: string]: unknown } = $props();
7
- </script>
8
-
9
- <DrawerPrimitive.Description class={cn("text-muted-foreground text-sm", className)} {...restProps}>
10
- {@render children?.()}
11
- </DrawerPrimitive.Description>
@@ -1,11 +0,0 @@
1
- <script lang="ts">
2
- import { cn } from '../../../utils.js';
3
- import type { Snippet } from 'svelte';
4
- import type { HTMLAttributes } from 'svelte/elements';
5
-
6
- let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
7
- </script>
8
-
9
- <div data-slot="drawer-footer" class={cn("mt-auto flex flex-col gap-2 p-4", className)} {...restProps}>
10
- {@render children?.()}
11
- </div>
@@ -1,11 +0,0 @@
1
- <script lang="ts">
2
- import { cn } from '../../../utils.js';
3
- import type { Snippet } from 'svelte';
4
- import type { HTMLAttributes } from 'svelte/elements';
5
-
6
- let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
7
- </script>
8
-
9
- <div data-slot="drawer-header" class={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...restProps}>
10
- {@render children?.()}
11
- </div>
@@ -1,11 +0,0 @@
1
- <script lang="ts">
2
- import { Drawer as DrawerPrimitive } from 'vaul-svelte';
3
- import { cn } from '../../../utils.js';
4
- import type { Snippet } from 'svelte';
5
-
6
- let { class: className, children, ...restProps }: { class?: string; children?: Snippet; [key: string]: unknown } = $props();
7
- </script>
8
-
9
- <DrawerPrimitive.Title class={cn("text-lg font-semibold leading-none tracking-tight", className)} {...restProps}>
10
- {@render children?.()}
11
- </DrawerPrimitive.Title>
@@ -1,18 +0,0 @@
1
- <script lang="ts">
2
- import { Drawer as DrawerPrimitive } from 'vaul-svelte';
3
- import type { Snippet } from 'svelte';
4
-
5
- let {
6
- open = $bindable(false),
7
- children,
8
- ...restProps
9
- }: {
10
- open?: boolean;
11
- children?: Snippet;
12
- [key: string]: unknown;
13
- } = $props();
14
- </script>
15
-
16
- <DrawerPrimitive.Root bind:open {...restProps}>
17
- {@render children?.()}
18
- </DrawerPrimitive.Root>
@@ -1,6 +0,0 @@
1
- export { default as Root } from './drawer.svelte';
2
- export { default as Content } from './drawer-content.svelte';
3
- export { default as Header } from './drawer-header.svelte';
4
- export { default as Footer } from './drawer-footer.svelte';
5
- export { default as Title } from './drawer-title.svelte';
6
- export { default as Description } from './drawer-description.svelte';