@svadmin/ui 0.37.0 → 0.38.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.
Files changed (46) hide show
  1. package/dist/app.css +42 -88
  2. package/dist/components/AdminApp.svelte +18 -14
  3. package/dist/components/CanAccess.svelte +3 -2
  4. package/dist/components/CanAccess.svelte.d.ts +1 -0
  5. package/dist/components/CreateInferencer.svelte +13 -0
  6. package/dist/components/CreateInferencer.svelte.d.ts +7 -0
  7. package/dist/components/EditInferencer.svelte +21 -0
  8. package/dist/components/EditInferencer.svelte.d.ts +9 -0
  9. package/dist/components/Header.svelte +4 -4
  10. package/dist/components/Layout.svelte +17 -7
  11. package/dist/components/ListInferencer.svelte +14 -0
  12. package/dist/components/ListInferencer.svelte.d.ts +7 -0
  13. package/dist/components/ResourceInferencer.svelte +28 -0
  14. package/dist/components/ResourceInferencer.svelte.d.ts +7 -0
  15. package/dist/components/ResourceOperationsPage.svelte +168 -21
  16. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -0
  17. package/dist/components/SettingsPage.svelte +3 -2
  18. package/dist/components/ShowInferencer.svelte +21 -0
  19. package/dist/components/ShowInferencer.svelte.d.ts +9 -0
  20. package/dist/components/ShowPage.svelte +2 -2
  21. package/dist/components/ShowPage.svelte.d.ts +1 -0
  22. package/dist/components/Sidebar.svelte +101 -104
  23. package/dist/components/Sidebar.svelte.d.ts +0 -1
  24. package/dist/components/SidebarItem.svelte +114 -52
  25. package/dist/components/account/TeamMembersPage.svelte +9 -3
  26. package/dist/components/buttons/CloneButton.svelte +10 -2
  27. package/dist/components/buttons/CloneButton.svelte.d.ts +2 -4
  28. package/dist/components/buttons/CreateButton.svelte +9 -2
  29. package/dist/components/buttons/CreateButton.svelte.d.ts +2 -4
  30. package/dist/components/buttons/DeleteButton.svelte +10 -2
  31. package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -4
  32. package/dist/components/buttons/EditButton.svelte +10 -2
  33. package/dist/components/buttons/EditButton.svelte.d.ts +2 -4
  34. package/dist/components/buttons/ExportButton.svelte +9 -2
  35. package/dist/components/buttons/ExportButton.svelte.d.ts +2 -4
  36. package/dist/components/buttons/ImportButton.svelte +9 -2
  37. package/dist/components/buttons/ImportButton.svelte.d.ts +2 -4
  38. package/dist/components/buttons/ShowButton.svelte +10 -2
  39. package/dist/components/buttons/ShowButton.svelte.d.ts +2 -4
  40. package/dist/components/buttons/access-control.d.ts +7 -0
  41. package/dist/components/buttons/access-control.js +5 -0
  42. package/dist/components/ui/sheet/sheet.svelte +3 -3
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.js +5 -0
  45. package/dist/router-state.svelte.js +15 -0
  46. package/package.json +2 -2
@@ -2,12 +2,14 @@
2
2
  import type { MenuItem } from '@svadmin/core';
3
3
  import * as Collapsible from './ui/collapsible/index.js';
4
4
  import * as Tooltip from './ui/tooltip/index.js';
5
+ import { Badge } from './ui/badge/index.js';
5
6
  import SidebarItem from './SidebarItem.svelte';
6
7
  import {
7
8
  LayoutDashboard, FileText, Users, Settings, Home,
8
9
  ChevronDown, Folder, ExternalLink, Repeat, ClipboardCheck, SlidersHorizontal, AlertTriangle,
9
- Mail, Send, ShoppingBag, Briefcase, Building2, Calendar, Bell, Clock, Trash2, Palette, Layout,
10
+ Mail, Map as MapIcon, MessageSquare, Send, ShoppingBag, Star, Briefcase, Building2, Calendar, Bell, Clock, Tag, Trash2, Palette, Layout,
10
11
  Download, ListTodo, TrendingUp, Sparkles, Images, Bot, Key, KeyRound, CreditCard, BookOpen, Wrench, Shield,
12
+ Circle,
11
13
  } from '@lucide/svelte';
12
14
 
13
15
  import { formatLink } from '@svadmin/core/router';
@@ -32,13 +34,17 @@
32
34
  'sliders-horizontal': SlidersHorizontal,
33
35
  'alert-triangle': AlertTriangle,
34
36
  mail: Mail,
37
+ map: MapIcon,
38
+ 'message-square': MessageSquare,
35
39
  send: Send,
36
40
  'shopping-bag': ShoppingBag,
41
+ star: Star,
37
42
  briefcase: Briefcase,
38
43
  building: Building2,
39
44
  calendar: Calendar,
40
45
  bell: Bell,
41
46
  clock: Clock,
47
+ tag: Tag,
42
48
  trash: Trash2,
43
49
  palette: Palette,
44
50
  layout: Layout,
@@ -57,7 +63,7 @@
57
63
  };
58
64
 
59
65
  function getIcon(name?: unknown): typeof LayoutDashboard {
60
- if (!name) return depth === 0 ? Settings : Folder;
66
+ if (!name) return depth === 0 ? Settings : Circle;
61
67
  if (typeof name !== 'string') return name as typeof LayoutDashboard;
62
68
  return iconMap[name] ?? Settings;
63
69
  }
@@ -68,11 +74,12 @@
68
74
 
69
75
  function isActive(itemHref: string | undefined): boolean {
70
76
  if (!itemHref) return false;
71
- if (itemHref === '/') return currentPath === '/';
72
- return currentPath.startsWith(itemHref) && (currentPath.length === itemHref.length || currentPath[itemHref.length] === '/');
77
+ const baseHref = itemHref.replace(/^#/, '').split(/[?#]/)[0] || '/';
78
+ const baseCurrentPath = currentPath.replace(/^#/, '').split(/[?#]/)[0] || '/';
79
+ if (baseHref === '/') return baseCurrentPath === '/';
80
+ return baseCurrentPath.startsWith(baseHref) && (baseCurrentPath.length === baseHref.length || baseCurrentPath[baseHref.length] === '/');
73
81
  }
74
82
 
75
- /** Check if any child (recursively) is active — used to auto-open parent groups */
76
83
  function hasActiveChild(menuItem: MenuItem): boolean {
77
84
  if (isActive(menuItem.href)) return true;
78
85
  return menuItem.children?.some((c: MenuItem) => hasActiveChild(c)) ?? false;
@@ -85,42 +92,73 @@
85
92
 
86
93
  const finalHref = $derived(isExternal ? item.href : (item.href ? formatLink(item.href) : undefined));
87
94
 
88
- // Track open state — auto-open if a child is active
89
95
  let isOpen = $state(false);
90
96
  $effect(() => { if (childActive) isOpen = true; });
91
97
 
92
98
  const Icon = $derived(getIcon(item.icon));
93
99
  const label = $derived(getLabel(item));
100
+ const soon = $derived(Boolean((item.meta as { soon?: boolean } | undefined)?.soon));
101
+
102
+ const isTopLevel = $derived(depth === 0);
103
+ const indentPx = $derived(isTopLevel ? 10 : 10 + depth * 16);
94
104
  </script>
95
105
 
96
106
  {#if item.meta?.hidden}
97
- <!-- hidden item, render nothing -->
107
+ <!-- hidden -->
98
108
  {:else if hasChildren && !collapsed}
99
- <!-- Parent node with children: render as collapsible group -->
100
- <Collapsible.Root bind:open={isOpen}>
101
- <Collapsible.Trigger
102
- class="flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
103
- {childActive
104
- ? 'text-sidebar-foreground bg-sidebar-accent/30'
105
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
106
- style="padding-left: {12 + depth * 12}px"
107
- >
108
- <span class="flex items-center gap-3">
109
- <Icon class="h-4 w-4 flex-shrink-0" />
110
- <span>{label}</span>
111
- </span>
112
- <ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
113
- </Collapsible.Trigger>
114
- <Collapsible.Content>
115
- <div class="mt-0.5 space-y-0.5" style="padding-left: {depth > 0 ? 0 : 0}px">
116
- {#each item.children as child, _i (_i)}
117
- <SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
118
- {/each}
119
- </div>
120
- </Collapsible.Content>
121
- </Collapsible.Root>
109
+ {#if isTopLevel}
110
+ <div class="mt-3 first:mt-0">
111
+ <Collapsible.Root bind:open={isOpen}>
112
+ <Collapsible.Trigger
113
+ class="sidebar-menu-item flex w-full items-center justify-between rounded-md py-[7px] text-[13px] font-medium transition-colors duration-150
114
+ {childActive
115
+ ? 'text-sidebar-foreground'
116
+ : 'text-sidebar-foreground/70 hover:text-sidebar-foreground'}"
117
+ style="padding-left: {indentPx}px; padding-right: 10px"
118
+ >
119
+ <span class="flex items-center gap-2.5">
120
+ <Icon class="h-[18px] w-[18px] flex-shrink-0 {childActive ? 'text-primary' : 'text-sidebar-foreground/50'}" />
121
+ <span>{label}</span>
122
+ </span>
123
+ <ChevronDown class="h-3.5 w-3.5 text-sidebar-foreground/40 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
124
+ </Collapsible.Trigger>
125
+ <Collapsible.Content>
126
+ <div class="relative mt-[2px]">
127
+ <div class="absolute left-[19px] top-0 bottom-0 w-px bg-border/60"></div>
128
+ <div class="space-y-[1px]">
129
+ {#each item.children as child, _i (_i)}
130
+ <SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
131
+ {/each}
132
+ </div>
133
+ </div>
134
+ </Collapsible.Content>
135
+ </Collapsible.Root>
136
+ </div>
137
+ {:else}
138
+ <Collapsible.Root bind:open={isOpen}>
139
+ <Collapsible.Trigger
140
+ class="sidebar-menu-item flex w-full items-center justify-between rounded-md py-[6px] text-[13px] font-normal transition-colors duration-150
141
+ {childActive
142
+ ? 'text-primary font-medium'
143
+ : 'text-sidebar-foreground/65 hover:text-sidebar-foreground'}"
144
+ style="padding-left: {indentPx}px; padding-right: 10px"
145
+ >
146
+ <span class="flex items-center gap-2.5">
147
+ <Circle class="h-[5px] w-[5px] flex-shrink-0 {childActive ? 'fill-primary text-primary' : 'fill-sidebar-foreground/30 text-sidebar-foreground/30'}" />
148
+ <span>{label}</span>
149
+ </span>
150
+ <ChevronDown class="h-3 w-3 text-sidebar-foreground/35 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
151
+ </Collapsible.Trigger>
152
+ <Collapsible.Content>
153
+ <div class="space-y-[1px] mt-[1px]">
154
+ {#each item.children as child, _i (_i)}
155
+ <SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
156
+ {/each}
157
+ </div>
158
+ </Collapsible.Content>
159
+ </Collapsible.Root>
160
+ {/if}
122
161
  {:else if collapsed}
123
- <!-- Collapsed sidebar: icon-only with tooltip -->
124
162
  <Tooltip.Root>
125
163
  <Tooltip.Trigger>
126
164
  {#snippet child({ props }: { props: Record<string, unknown> })}
@@ -129,12 +167,12 @@
129
167
  href={finalHref}
130
168
  target={isExternal ? '_blank' : undefined}
131
169
  rel={isExternal ? 'noopener noreferrer' : undefined}
132
- class="flex items-center justify-center rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
170
+ class="sidebar-menu-item flex items-center justify-center rounded-md px-2 py-[7px] transition-colors duration-150
133
171
  {active
134
- ? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5'
135
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
172
+ ? 'sidebar-menu-item-active bg-sidebar-accent text-primary'
173
+ : 'text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
136
174
  >
137
- <Icon class="h-4 w-4 flex-shrink-0" />
175
+ <Icon class="h-[18px] w-[18px] flex-shrink-0" />
138
176
  </a>
139
177
  {/snippet}
140
178
  </Tooltip.Trigger>
@@ -143,21 +181,45 @@
143
181
  </Tooltip.Content>
144
182
  </Tooltip.Root>
145
183
  {:else}
146
- <!-- Leaf node: render as a link -->
147
- <a
148
- href={finalHref}
149
- target={isExternal ? '_blank' : undefined}
150
- rel={isExternal ? 'noopener noreferrer' : undefined}
151
- class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
152
- {active
153
- ? 'bg-card text-sidebar-primary shadow-sm ring-1 ring-foreground/5 font-semibold before:absolute before:left-0 before:top-2 before:bottom-2 before:w-[3px] before:bg-primary before:rounded-r-md overflow-hidden relative'
154
- : 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
155
- style="padding-left: {12 + depth * 12}px"
156
- >
157
- <Icon class="h-4 w-4 flex-shrink-0" />
158
- <span class="flex-1">{label}</span>
159
- {#if isExternal}
160
- <ExternalLink class="h-3 w-3 opacity-50" />
161
- {/if}
162
- </a>
184
+ {#if isTopLevel}
185
+ <a
186
+ href={finalHref}
187
+ target={isExternal ? '_blank' : undefined}
188
+ rel={isExternal ? 'noopener noreferrer' : undefined}
189
+ class="sidebar-menu-item flex items-center gap-2.5 rounded-md py-[7px] text-[13px] font-medium transition-colors duration-150
190
+ {active
191
+ ? 'sidebar-menu-item-active text-primary bg-sidebar-accent'
192
+ : 'text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent/50'}"
193
+ style="padding-left: {indentPx}px; padding-right: 10px"
194
+ >
195
+ <Icon class="h-[18px] w-[18px] flex-shrink-0 {active ? 'text-primary' : 'text-sidebar-foreground/50'}" />
196
+ <span class="flex-1">{label}</span>
197
+ {#if soon}
198
+ <Badge variant="secondary" class="h-[18px] px-1.5 text-[10px] font-medium">Soon</Badge>
199
+ {/if}
200
+ {#if isExternal}
201
+ <ExternalLink class="h-3 w-3 opacity-40" />
202
+ {/if}
203
+ </a>
204
+ {:else}
205
+ <a
206
+ href={finalHref}
207
+ target={isExternal ? '_blank' : undefined}
208
+ rel={isExternal ? 'noopener noreferrer' : undefined}
209
+ class="sidebar-menu-item flex items-center gap-2.5 rounded-md py-[6px] text-[13px] font-normal transition-colors duration-150
210
+ {active
211
+ ? 'sidebar-menu-item-active text-primary font-medium'
212
+ : 'text-sidebar-foreground/65 hover:text-sidebar-foreground'}"
213
+ style="padding-left: {indentPx}px; padding-right: 10px"
214
+ >
215
+ <Circle class="h-[5px] w-[5px] flex-shrink-0 {active ? 'fill-primary text-primary' : 'fill-sidebar-foreground/30 text-sidebar-foreground/30'}" />
216
+ <span class="flex-1">{label}</span>
217
+ {#if soon}
218
+ <Badge variant="secondary" class="h-[18px] px-1.5 text-[10px] font-medium">Soon</Badge>
219
+ {/if}
220
+ {#if isExternal}
221
+ <ExternalLink class="h-3 w-3 opacity-40" />
222
+ {/if}
223
+ </a>
224
+ {/if}
163
225
  {/if}
@@ -46,13 +46,19 @@
46
46
  };
47
47
 
48
48
  const initials = (name: string) => name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
49
+
50
+ function statusLabel(status: TeamMember['status']) {
51
+ if (status === 'active') return t('account.active');
52
+ if (status === 'invited') return t('account.invited');
53
+ return t('account.inactive');
54
+ }
49
55
  </script>
50
56
 
51
57
  <div class="space-y-6" data-svadmin-content-page="account">
52
58
  <div class="flex items-start justify-between gap-4">
53
59
  <div>
54
- <h2 class="text-xl font-semibold text-foreground">{t('account.membersStarter')}</h2>
55
- <p class="mt-1 text-sm text-muted-foreground">{t('account.membersStarterDescription')}</p>
60
+ <h2 class="text-xl font-semibold text-foreground">{t('account.teamMembers')}</h2>
61
+ <p class="mt-1 text-sm text-muted-foreground">{t('account.teamMembersDescription')}</p>
56
62
  </div>
57
63
  <Button size="sm">
58
64
  <UserPlus class="h-4 w-4 mr-1" />{t('account.inviteMember')}
@@ -82,7 +88,7 @@
82
88
  <p class="text-xs text-muted-foreground truncate">{member.email}</p>
83
89
  <div class="mt-2 flex flex-wrap gap-1.5">
84
90
  <span class="rounded-full px-2 py-0.5 text-[10px] font-semibold {roleColors[member.role]}">{member.role}</span>
85
- <span class="rounded-full px-2 py-0.5 text-[10px] font-semibold {statusColors[member.status]}">{member.status}</span>
91
+ <span class="rounded-full px-2 py-0.5 text-[10px] font-semibold {statusColors[member.status]}">{statusLabel(member.status)}</span>
86
92
  </div>
87
93
  <div class="mt-2 flex items-center justify-between">
88
94
  <span class="text-xs text-muted-foreground">{member.department}</span>
@@ -2,17 +2,25 @@
2
2
  import { useNavigation, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Copy } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
6
+ import { withRecordId } from './access-control';
5
7
 
6
8
  let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
9
  resource: string;
8
10
  recordItemId: string | number;
9
11
  hideText?: boolean;
10
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
12
+ accessControl?: ButtonAccessControl;
11
13
  class?: string;
12
14
  }>();
13
15
 
14
16
  const nav = useNavigation();
15
- const can = useCan(() => ({ resource, action: 'create', queryOptions: { enabled: accessControl?.enabled ?? true } }));
17
+ const can = useCan(() => ({
18
+ resource,
19
+ action: 'create',
20
+ params: withRecordId(accessControl?.params, recordItemId),
21
+ meta: accessControl?.meta,
22
+ queryOptions: { enabled: accessControl?.enabled ?? true }
23
+ }));
16
24
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
17
25
  </script>
18
26
 
@@ -1,11 +1,9 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  recordItemId: string | number;
4
5
  hideText?: boolean;
5
- accessControl?: {
6
- enabled?: boolean;
7
- hideIfUnauthorized?: boolean;
8
- };
6
+ accessControl?: ButtonAccessControl;
9
7
  class?: string;
10
8
  };
11
9
  declare const CloneButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -2,16 +2,23 @@
2
2
  import { useNavigation, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Plus } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
5
6
 
6
7
  let { resource, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
8
  resource: string;
8
9
  hideText?: boolean;
9
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
10
+ accessControl?: ButtonAccessControl;
10
11
  class?: string;
11
12
  }>();
12
13
 
13
14
  const nav = useNavigation();
14
- const can = useCan(() => ({ resource, action: 'create', queryOptions: { enabled: accessControl?.enabled ?? true } }));
15
+ const can = useCan(() => ({
16
+ resource,
17
+ action: 'create',
18
+ params: accessControl?.params,
19
+ meta: accessControl?.meta,
20
+ queryOptions: { enabled: accessControl?.enabled ?? true }
21
+ }));
15
22
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
16
23
  </script>
17
24
 
@@ -1,10 +1,8 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  hideText?: boolean;
4
- accessControl?: {
5
- enabled?: boolean;
6
- hideIfUnauthorized?: boolean;
7
- };
5
+ accessControl?: ButtonAccessControl;
8
6
  class?: string;
9
7
  };
10
8
  declare const CreateButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -2,6 +2,8 @@
2
2
  import { useDelete, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Trash2 } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
6
+ import { withRecordId } from './access-control';
5
7
 
6
8
  let {
7
9
  resource, recordItemId, hideText = false,
@@ -10,14 +12,20 @@
10
12
  resource: string;
11
13
  recordItemId: string | number;
12
14
  hideText?: boolean;
13
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
15
+ accessControl?: ButtonAccessControl;
14
16
  onSuccess?: () => void;
15
17
  undoable?: boolean;
16
18
  class?: string;
17
19
  }>();
18
20
 
19
21
  const deleteMut = useDelete({ get resource() { return resource; }, get mutationMode() { return undoable ? 'undoable' as const : 'pessimistic' as const; } });
20
- const can = useCan(() => ({ resource, action: 'delete', queryOptions: { enabled: accessControl?.enabled ?? true } }));
22
+ const can = useCan(() => ({
23
+ resource,
24
+ action: 'delete',
25
+ params: withRecordId(accessControl?.params, recordItemId),
26
+ meta: accessControl?.meta,
27
+ queryOptions: { enabled: accessControl?.enabled ?? true }
28
+ }));
21
29
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
22
30
  let confirming = $state(false);
23
31
 
@@ -1,11 +1,9 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  recordItemId: string | number;
4
5
  hideText?: boolean;
5
- accessControl?: {
6
- enabled?: boolean;
7
- hideIfUnauthorized?: boolean;
8
- };
6
+ accessControl?: ButtonAccessControl;
9
7
  onSuccess?: () => void;
10
8
  undoable?: boolean;
11
9
  class?: string;
@@ -2,17 +2,25 @@
2
2
  import { useNavigation, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Pencil } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
6
+ import { withRecordId } from './access-control';
5
7
 
6
8
  let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
9
  resource: string;
8
10
  recordItemId: string | number;
9
11
  hideText?: boolean;
10
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
12
+ accessControl?: ButtonAccessControl;
11
13
  class?: string;
12
14
  }>();
13
15
 
14
16
  const nav = useNavigation();
15
- const can = useCan(() => ({ resource, action: 'edit', queryOptions: { enabled: accessControl?.enabled ?? true } }));
17
+ const can = useCan(() => ({
18
+ resource,
19
+ action: 'edit',
20
+ params: withRecordId(accessControl?.params, recordItemId),
21
+ meta: accessControl?.meta,
22
+ queryOptions: { enabled: accessControl?.enabled ?? true }
23
+ }));
16
24
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
17
25
  </script>
18
26
 
@@ -1,11 +1,9 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  recordItemId: string | number;
4
5
  hideText?: boolean;
5
- accessControl?: {
6
- enabled?: boolean;
7
- hideIfUnauthorized?: boolean;
8
- };
6
+ accessControl?: ButtonAccessControl;
9
7
  class?: string;
10
8
  };
11
9
  declare const EditButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -2,16 +2,23 @@
2
2
  import { useExport, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Download } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
5
6
 
6
7
  let { resource, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
8
  resource: string;
8
9
  hideText?: boolean;
9
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
10
+ accessControl?: ButtonAccessControl;
10
11
  class?: string;
11
12
  }>();
12
13
 
13
14
  const { triggerExport, isLoading } = useExport({ get resource() { return resource; } });
14
- const can = useCan(() => ({ resource, action: 'export', queryOptions: { enabled: accessControl?.enabled ?? true } }));
15
+ const can = useCan(() => ({
16
+ resource,
17
+ action: 'export',
18
+ params: accessControl?.params,
19
+ meta: accessControl?.meta,
20
+ queryOptions: { enabled: accessControl?.enabled ?? true }
21
+ }));
15
22
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
16
23
  </script>
17
24
 
@@ -1,10 +1,8 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  hideText?: boolean;
4
- accessControl?: {
5
- enabled?: boolean;
6
- hideIfUnauthorized?: boolean;
7
- };
5
+ accessControl?: ButtonAccessControl;
8
6
  class?: string;
9
7
  };
10
8
  declare const ExportButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -2,12 +2,13 @@
2
2
  import { useImport, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Upload } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
5
6
 
6
7
  let { resource, hideText = false, onFinish, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
8
  resource: string;
8
9
  hideText?: boolean;
9
10
  onFinish?: (result: { succeeded: unknown[]; errored: { request: unknown; error: unknown }[] }) => void;
10
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
11
+ accessControl?: ButtonAccessControl;
11
12
  class?: string;
12
13
  }>();
13
14
 
@@ -31,7 +32,13 @@
31
32
  }
32
33
  }
33
34
 
34
- const can = useCan(() => ({ resource, action: 'import', queryOptions: { enabled: accessControl?.enabled ?? true } }));
35
+ const can = useCan(() => ({
36
+ resource,
37
+ action: 'import',
38
+ params: accessControl?.params,
39
+ meta: accessControl?.meta,
40
+ queryOptions: { enabled: accessControl?.enabled ?? true }
41
+ }));
35
42
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
36
43
  </script>
37
44
 
@@ -1,3 +1,4 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  hideText?: boolean;
@@ -8,10 +9,7 @@ type $$ComponentProps = {
8
9
  error: unknown;
9
10
  }[];
10
11
  }) => void;
11
- accessControl?: {
12
- enabled?: boolean;
13
- hideIfUnauthorized?: boolean;
14
- };
12
+ accessControl?: ButtonAccessControl;
15
13
  class?: string;
16
14
  };
17
15
  declare const ImportButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -2,17 +2,25 @@
2
2
  import { useNavigation, useCan, t } from '@svadmin/core';
3
3
  import { Button } from '../ui/button/index.js';
4
4
  import { Eye } from '@lucide/svelte';
5
+ import type { ButtonAccessControl } from './access-control';
6
+ import { withRecordId } from './access-control';
5
7
 
6
8
  let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
7
9
  resource: string;
8
10
  recordItemId: string | number;
9
11
  hideText?: boolean;
10
- accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
12
+ accessControl?: ButtonAccessControl;
11
13
  class?: string;
12
14
  }>();
13
15
 
14
16
  const nav = useNavigation();
15
- const can = useCan(() => ({ resource, action: 'show', queryOptions: { enabled: accessControl?.enabled ?? true } }));
17
+ const can = useCan(() => ({
18
+ resource,
19
+ action: 'show',
20
+ params: withRecordId(accessControl?.params, recordItemId),
21
+ meta: accessControl?.meta,
22
+ queryOptions: { enabled: accessControl?.enabled ?? true }
23
+ }));
16
24
  const hidden = $derived(accessControl?.hideIfUnauthorized && !can.allowed);
17
25
  </script>
18
26
 
@@ -1,11 +1,9 @@
1
+ import type { ButtonAccessControl } from './access-control';
1
2
  type $$ComponentProps = {
2
3
  resource: string;
3
4
  recordItemId: string | number;
4
5
  hideText?: boolean;
5
- accessControl?: {
6
- enabled?: boolean;
7
- hideIfUnauthorized?: boolean;
8
- };
6
+ accessControl?: ButtonAccessControl;
9
7
  class?: string;
10
8
  };
11
9
  declare const ShowButton: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -0,0 +1,7 @@
1
+ export interface ButtonAccessControl {
2
+ enabled?: boolean;
3
+ hideIfUnauthorized?: boolean;
4
+ params?: Record<string, unknown>;
5
+ meta?: Record<string, unknown>;
6
+ }
7
+ export declare function withRecordId(params: Record<string, unknown> | undefined, recordItemId: string | number | undefined): Record<string, unknown> | undefined;
@@ -0,0 +1,5 @@
1
+ export function withRecordId(params, recordItemId) {
2
+ return recordItemId === undefined
3
+ ? params
4
+ : { id: recordItemId, ...params };
5
+ }
@@ -54,10 +54,10 @@
54
54
  bind:this={ref}
55
55
  data-slot="sheet"
56
56
  class={cn(
57
- "fixed z-50 flex flex-col gap-4 bg-background p-6 shadow-lg transition-transform",
57
+ "fixed z-50 flex translate-x-0 flex-col gap-4 bg-background p-6 shadow-lg",
58
58
  side === "right"
59
- ? "inset-y-0 right-0 h-full w-3/4 max-w-sm border-l animate-in slide-in-from-right"
60
- : "inset-y-0 left-0 h-full w-3/4 max-w-sm border-r animate-in slide-in-from-left",
59
+ ? "inset-y-0 right-0 h-full w-3/4 max-w-sm border-l"
60
+ : "inset-y-0 left-0 h-full w-3/4 max-w-sm border-r",
61
61
  className
62
62
  )}
63
63
  {...restProps}
package/dist/index.d.ts CHANGED
@@ -28,6 +28,11 @@ export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.s
28
28
  export { default as ProfilePage } from './components/ProfilePage.svelte';
29
29
  export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
30
30
  export { default as InferencerPanel } from './components/InferencerPanel.svelte';
31
+ export { default as ListInferencer } from './components/ListInferencer.svelte';
32
+ export { default as CreateInferencer } from './components/CreateInferencer.svelte';
33
+ export { default as EditInferencer } from './components/EditInferencer.svelte';
34
+ export { default as ShowInferencer } from './components/ShowInferencer.svelte';
35
+ export { default as ResourceInferencer } from './components/ResourceInferencer.svelte';
31
36
  export { default as LiveIndicator } from './components/LiveIndicator.svelte';
32
37
  export { default as ListPage } from './components/ListPage.svelte';
33
38
  export { default as CreatePage } from './components/CreatePage.svelte';
package/dist/index.js CHANGED
@@ -31,6 +31,11 @@ export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.s
31
31
  export { default as ProfilePage } from './components/ProfilePage.svelte';
32
32
  export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
33
33
  export { default as InferencerPanel } from './components/InferencerPanel.svelte';
34
+ export { default as ListInferencer } from './components/ListInferencer.svelte';
35
+ export { default as CreateInferencer } from './components/CreateInferencer.svelte';
36
+ export { default as EditInferencer } from './components/EditInferencer.svelte';
37
+ export { default as ShowInferencer } from './components/ShowInferencer.svelte';
38
+ export { default as ResourceInferencer } from './components/ResourceInferencer.svelte';
34
39
  export { default as LiveIndicator } from './components/LiveIndicator.svelte';
35
40
  export { default as ListPage } from './components/ListPage.svelte';
36
41
  export { default as CreatePage } from './components/CreatePage.svelte';