@svadmin/ui 0.0.3 → 0.0.6

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.
@@ -0,0 +1,60 @@
1
+ <script lang="ts">
2
+ import { useDelete, useCan, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Trash2 } from 'lucide-svelte';
5
+
6
+ let {
7
+ resource, recordItemId, hideText = false,
8
+ accessControl, onSuccess, undoable = false, class: className = '',
9
+ } = $props<{
10
+ resource: string;
11
+ recordItemId: string | number;
12
+ hideText?: boolean;
13
+ accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
14
+ onSuccess?: () => void;
15
+ undoable?: boolean;
16
+ class?: string;
17
+ }>();
18
+
19
+ const deleteMut = useDelete(resource, { mutationMode: undoable ? 'undoable' : 'pessimistic' });
20
+ const can = accessControl?.enabled ? useCan({ resource, action: 'delete' }) : null;
21
+ const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
22
+ let confirming = $state(false);
23
+
24
+ async function handleDelete() {
25
+ if (!confirming) {
26
+ confirming = true;
27
+ return;
28
+ }
29
+ confirming = false;
30
+ // @ts-expect-error $ rune prefix
31
+ await $deleteMut.mutateAsync(recordItemId);
32
+ onSuccess?.();
33
+ }
34
+
35
+ function cancel() { confirming = false; }
36
+ </script>
37
+
38
+ {#if !hidden}
39
+ {#if confirming}
40
+ <div class="inline-flex items-center gap-1">
41
+ <Button variant="destructive" size="sm" onclick={handleDelete}>
42
+ {t('common.confirm')}
43
+ </Button>
44
+ <Button variant="ghost" size="sm" onclick={cancel}>
45
+ {t('common.cancel')}
46
+ </Button>
47
+ </div>
48
+ {:else}
49
+ <Button
50
+ variant="ghost"
51
+ size={hideText ? 'icon' : 'sm'}
52
+ class="text-destructive hover:text-destructive {className}"
53
+ disabled={can ? !can.allowed : false}
54
+ onclick={handleDelete}
55
+ >
56
+ <Trash2 class="h-4 w-4" />
57
+ {#if !hideText}<span class="ml-1">{t('common.delete')}</span>{/if}
58
+ </Button>
59
+ {/if}
60
+ {/if}
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { useNavigation, useCan, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Pencil } from 'lucide-svelte';
5
+
6
+ let { resource, recordItemId, hideText = false, accessControl, class: className = '' } = $props<{
7
+ resource: string;
8
+ recordItemId: string | number;
9
+ hideText?: boolean;
10
+ accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
11
+ class?: string;
12
+ }>();
13
+
14
+ const nav = useNavigation();
15
+ const can = accessControl?.enabled ? useCan({ resource, action: 'edit' }) : null;
16
+ const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
17
+ </script>
18
+
19
+ {#if !hidden}
20
+ <Button
21
+ variant="outline"
22
+ size={hideText ? 'icon' : 'sm'}
23
+ class={className}
24
+ disabled={can ? !can.allowed : false}
25
+ onclick={() => nav.edit(resource, recordItemId)}
26
+ >
27
+ <Pencil class="h-4 w-4" />
28
+ {#if !hideText}<span class="ml-1">{t('common.edit')}</span>{/if}
29
+ </Button>
30
+ {/if}
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import { useExport, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Download } from 'lucide-svelte';
5
+
6
+ let { resource, hideText = false, class: className = '' } = $props<{
7
+ resource: string;
8
+ hideText?: boolean;
9
+ class?: string;
10
+ }>();
11
+
12
+ const { triggerExport, isLoading } = useExport({ resource });
13
+ </script>
14
+
15
+ <Button
16
+ variant="outline"
17
+ size={hideText ? 'icon' : 'sm'}
18
+ class={className}
19
+ disabled={isLoading}
20
+ onclick={triggerExport}
21
+ >
22
+ <Download class="h-4 w-4" />
23
+ {#if !hideText}<span class="ml-1">{t('common.export')}</span>{/if}
24
+ </Button>
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { useImport, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Upload } from 'lucide-svelte';
5
+
6
+ let { resource, hideText = false, onComplete, class: className = '' } = $props<{
7
+ resource: string;
8
+ hideText?: boolean;
9
+ onComplete?: (result: { success: number; failed: number }) => void;
10
+ class?: string;
11
+ }>();
12
+
13
+ const { triggerImport, isLoading } = useImport({
14
+ resource,
15
+ onComplete: (result) => onComplete?.(result),
16
+ });
17
+ </script>
18
+
19
+ <Button
20
+ variant="outline"
21
+ size={hideText ? 'icon' : 'sm'}
22
+ class={className}
23
+ disabled={isLoading}
24
+ onclick={triggerImport}
25
+ >
26
+ <Upload class="h-4 w-4" />
27
+ {#if !hideText}<span class="ml-1">{t('common.import')}</span>{/if}
28
+ </Button>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { useNavigation, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { List } from 'lucide-svelte';
5
+
6
+ let { resource, hideText = false, class: className = '' } = $props<{
7
+ resource: string;
8
+ hideText?: boolean;
9
+ class?: string;
10
+ }>();
11
+
12
+ const nav = useNavigation();
13
+ </script>
14
+
15
+ <Button
16
+ variant="outline"
17
+ size={hideText ? 'icon' : 'sm'}
18
+ class={className}
19
+ onclick={() => nav.list(resource)}
20
+ >
21
+ <List class="h-4 w-4" />
22
+ {#if !hideText}<span class="ml-1">{resource}</span>{/if}
23
+ </Button>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { useQueryClient } from '@tanstack/svelte-query';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { RefreshCw } from 'lucide-svelte';
5
+
6
+ let { resource, hideText = false, class: className = '' } = $props<{
7
+ resource: string;
8
+ hideText?: boolean;
9
+ class?: string;
10
+ }>();
11
+
12
+ const queryClient = useQueryClient();
13
+ let spinning = $state(false);
14
+
15
+ function refresh() {
16
+ spinning = true;
17
+ queryClient.invalidateQueries({ queryKey: [resource] });
18
+ setTimeout(() => { spinning = false; }, 600);
19
+ }
20
+ </script>
21
+
22
+ <Button
23
+ variant="ghost"
24
+ size={hideText ? 'icon' : 'sm'}
25
+ class={className}
26
+ onclick={refresh}
27
+ >
28
+ <RefreshCw class="h-4 w-4 {spinning ? 'animate-spin' : ''}" />
29
+ {#if !hideText}<span class="ml-1">Refresh</span>{/if}
30
+ </Button>
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Save, Loader2 } from 'lucide-svelte';
5
+
6
+ let { loading = false, hideText = false, type = 'submit', class: className = '' } = $props<{
7
+ loading?: boolean;
8
+ hideText?: boolean;
9
+ type?: 'submit' | 'button';
10
+ class?: string;
11
+ }>();
12
+ </script>
13
+
14
+ <Button
15
+ {type}
16
+ variant="default"
17
+ size={hideText ? 'icon' : 'default'}
18
+ class={className}
19
+ disabled={loading}
20
+ >
21
+ {#if loading}
22
+ <Loader2 class="h-4 w-4 animate-spin" />
23
+ {:else}
24
+ <Save class="h-4 w-4" />
25
+ {/if}
26
+ {#if !hideText}<span class="ml-1">{t('common.save')}</span>{/if}
27
+ </Button>
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import { useNavigation, t } from '@svadmin/core';
3
+ import { Button } from '../ui/button/index.js';
4
+ import { Eye } from 'lucide-svelte';
5
+
6
+ let { resource, recordItemId, hideText = false, class: className = '' } = $props<{
7
+ resource: string;
8
+ recordItemId: string | number;
9
+ hideText?: boolean;
10
+ class?: string;
11
+ }>();
12
+
13
+ const nav = useNavigation();
14
+ </script>
15
+
16
+ <Button
17
+ variant="ghost"
18
+ size={hideText ? 'icon' : 'sm'}
19
+ class={className}
20
+ onclick={() => nav.show(resource, recordItemId)}
21
+ >
22
+ <Eye class="h-4 w-4" />
23
+ {#if !hideText}<span class="ml-1">{t('common.detail')}</span>{/if}
24
+ </Button>
@@ -0,0 +1,10 @@
1
+ export { default as CreateButton } from './CreateButton.svelte';
2
+ export { default as EditButton } from './EditButton.svelte';
3
+ export { default as DeleteButton } from './DeleteButton.svelte';
4
+ export { default as ShowButton } from './ShowButton.svelte';
5
+ export { default as ListButton } from './ListButton.svelte';
6
+ export { default as RefreshButton } from './RefreshButton.svelte';
7
+ export { default as ExportButton } from './ExportButton.svelte';
8
+ export { default as ImportButton } from './ImportButton.svelte';
9
+ export { default as SaveButton } from './SaveButton.svelte';
10
+ export { default as CloneButton } from './CloneButton.svelte';
package/src/index.ts CHANGED
@@ -26,6 +26,16 @@ export { default as UndoableNotification } from './components/UndoableNotificati
26
26
  export { default as ModalForm } from './components/ModalForm.svelte';
27
27
  export { default as DrawerForm } from './components/DrawerForm.svelte';
28
28
  export { default as DevTools } from './components/DevTools.svelte';
29
+ export { default as Authenticated } from './components/Authenticated.svelte';
30
+ export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.svelte';
31
+ export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
32
+ export { default as InferencerPanel } from './components/InferencerPanel.svelte';
33
+
34
+ // CRUD Buttons
35
+ export {
36
+ CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
37
+ RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton,
38
+ } from './components/buttons/index.js';
29
39
 
30
40
  // Base UI components (shadcn-svelte)
31
41
  export { Button, buttonVariants } from './components/ui/button/index.js';
@@ -1,14 +1,17 @@
1
1
  // Shared reactive hash router state — .svelte.ts enables $state at module level
2
2
  import { matchRoute } from '@svadmin/core/router';
3
+ import type { RouterProvider } from '@svadmin/core';
3
4
 
4
5
  let _route = $state('/');
5
6
  let _params: Record<string, string> = $state({});
6
7
  let _initialized = false;
8
+ let _provider: RouterProvider | undefined;
7
9
 
8
10
  const ROUTES = [
9
11
  '/login',
10
12
  '/register',
11
13
  '/forgot-password',
14
+ '/update-password',
12
15
  '/',
13
16
  '/:resource',
14
17
  '/:resource/create',
@@ -17,17 +20,29 @@ const ROUTES = [
17
20
  ];
18
21
 
19
22
  function sync() {
20
- const hash = window.location.hash;
21
- const m = matchRoute(hash, ROUTES);
22
- _route = m?.route ?? '/';
23
- _params = m?.params ?? {};
23
+ if (_provider) {
24
+ // Use RouterProvider for parsing
25
+ const parsed = _provider.parse();
26
+ const path = parsed.pathname || '/';
27
+ const m = matchRoute(path.startsWith('#') ? path : `#${path}`, ROUTES);
28
+ _route = m?.route ?? '/';
29
+ _params = { ...(m?.params ?? {}), ...parsed.params };
30
+ } else {
31
+ // Fallback: direct hash parsing
32
+ const hash = window.location.hash;
33
+ const m = matchRoute(hash, ROUTES);
34
+ _route = m?.route ?? '/';
35
+ _params = m?.params ?? {};
36
+ }
24
37
  }
25
38
 
26
- export function initRouter() {
39
+ export function initRouter(provider?: RouterProvider) {
27
40
  if (_initialized) return;
28
41
  _initialized = true;
42
+ _provider = provider;
29
43
  sync();
30
44
  window.addEventListener('hashchange', sync);
45
+ window.addEventListener('popstate', sync);
31
46
  }
32
47
 
33
48
  export function getRoute(): string {
@@ -37,3 +52,7 @@ export function getRoute(): string {
37
52
  export function getParams(): Record<string, string> {
38
53
  return _params;
39
54
  }
55
+
56
+ export function getRouterProviderInstance(): RouterProvider | undefined {
57
+ return _provider;
58
+ }