@svadmin/ui 0.36.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 (49) hide show
  1. package/dist/app.css +76 -104
  2. package/dist/components/AdminApp.svelte +18 -14
  3. package/dist/components/AutoForm.svelte +32 -47
  4. package/dist/components/AutoTable.svelte +26 -31
  5. package/dist/components/CanAccess.svelte +3 -2
  6. package/dist/components/CanAccess.svelte.d.ts +1 -0
  7. package/dist/components/CreateInferencer.svelte +13 -0
  8. package/dist/components/CreateInferencer.svelte.d.ts +7 -0
  9. package/dist/components/EditInferencer.svelte +21 -0
  10. package/dist/components/EditInferencer.svelte.d.ts +9 -0
  11. package/dist/components/Header.svelte +6 -6
  12. package/dist/components/Layout.svelte +21 -11
  13. package/dist/components/ListInferencer.svelte +14 -0
  14. package/dist/components/ListInferencer.svelte.d.ts +7 -0
  15. package/dist/components/PageHeader.svelte +7 -9
  16. package/dist/components/ResourceInferencer.svelte +28 -0
  17. package/dist/components/ResourceInferencer.svelte.d.ts +7 -0
  18. package/dist/components/ResourceOperationsPage.svelte +168 -21
  19. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -0
  20. package/dist/components/SettingsPage.svelte +76 -41
  21. package/dist/components/ShowInferencer.svelte +21 -0
  22. package/dist/components/ShowInferencer.svelte.d.ts +9 -0
  23. package/dist/components/ShowPage.svelte +11 -11
  24. package/dist/components/ShowPage.svelte.d.ts +1 -0
  25. package/dist/components/Sidebar.svelte +214 -300
  26. package/dist/components/Sidebar.svelte.d.ts +0 -1
  27. package/dist/components/SidebarItem.svelte +141 -108
  28. package/dist/components/account/TeamMembersPage.svelte +9 -3
  29. package/dist/components/buttons/CloneButton.svelte +10 -2
  30. package/dist/components/buttons/CloneButton.svelte.d.ts +2 -4
  31. package/dist/components/buttons/CreateButton.svelte +9 -2
  32. package/dist/components/buttons/CreateButton.svelte.d.ts +2 -4
  33. package/dist/components/buttons/DeleteButton.svelte +10 -2
  34. package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -4
  35. package/dist/components/buttons/EditButton.svelte +10 -2
  36. package/dist/components/buttons/EditButton.svelte.d.ts +2 -4
  37. package/dist/components/buttons/ExportButton.svelte +9 -2
  38. package/dist/components/buttons/ExportButton.svelte.d.ts +2 -4
  39. package/dist/components/buttons/ImportButton.svelte +9 -2
  40. package/dist/components/buttons/ImportButton.svelte.d.ts +2 -4
  41. package/dist/components/buttons/ShowButton.svelte +10 -2
  42. package/dist/components/buttons/ShowButton.svelte.d.ts +2 -4
  43. package/dist/components/buttons/access-control.d.ts +7 -0
  44. package/dist/components/buttons/access-control.js +5 -0
  45. package/dist/components/ui/sheet/sheet.svelte +3 -3
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.js +5 -0
  48. package/dist/router-state.svelte.js +15 -0
  49. package/package.json +2 -2
@@ -6,7 +6,6 @@ type $$ComponentProps = {
6
6
  onToggle: () => void;
7
7
  onLogout: () => void;
8
8
  menu?: MenuItem[];
9
- /** 'hash' for SPA hash routing, 'path' for SvelteKit/filesystem routing, 'auto' to detect */
10
9
  routeMode?: 'hash' | 'path' | 'auto';
11
10
  };
12
11
  declare const Sidebar: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -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
- Barcode, FolderTree, Building2, Warehouse, BriefcaseBusiness, Handshake, UserRoundCheck,
10
- CalendarCheck, KeyRound, MapPinned,
10
+ Mail, Map as MapIcon, MessageSquare, Send, ShoppingBag, Star, Briefcase, Building2, Calendar, Bell, Clock, Tag, Trash2, Palette, Layout,
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';
@@ -22,6 +24,7 @@
22
24
  const iconMap: Record<string, typeof LayoutDashboard> = {
23
25
  dashboard: LayoutDashboard,
24
26
  posts: FileText,
27
+ file: FileText,
25
28
  users: Users,
26
29
  settings: Settings,
27
30
  home: Home,
@@ -30,21 +33,38 @@
30
33
  'clipboard-check': ClipboardCheck,
31
34
  'sliders-horizontal': SlidersHorizontal,
32
35
  'alert-triangle': AlertTriangle,
33
- 'barcode': Barcode,
34
- 'folder-tree': FolderTree,
35
- 'building-2': Building2,
36
- 'warehouse': Warehouse,
37
- 'briefcase-business': BriefcaseBusiness,
38
- handshake: Handshake,
39
- 'user-round-check': UserRoundCheck,
40
- 'calendar-check': CalendarCheck,
36
+ mail: Mail,
37
+ map: MapIcon,
38
+ 'message-square': MessageSquare,
39
+ send: Send,
40
+ 'shopping-bag': ShoppingBag,
41
+ star: Star,
42
+ briefcase: Briefcase,
43
+ building: Building2,
44
+ calendar: Calendar,
45
+ bell: Bell,
46
+ clock: Clock,
47
+ tag: Tag,
48
+ trash: Trash2,
49
+ palette: Palette,
50
+ layout: Layout,
51
+ download: Download,
52
+ 'list-todo': ListTodo,
53
+ 'trending-up': TrendingUp,
54
+ sparkles: Sparkles,
55
+ images: Images,
56
+ bot: Bot,
57
+ key: Key,
41
58
  'key-round': KeyRound,
42
- 'map-pinned': MapPinned,
59
+ 'credit-card': CreditCard,
60
+ 'book-open': BookOpen,
61
+ wrench: Wrench,
62
+ shield: Shield,
43
63
  };
44
64
 
45
- function getIcon(name?: string | typeof LayoutDashboard): typeof LayoutDashboard {
46
- if (!name) return depth === 0 ? Settings : Folder;
47
- if (typeof name !== 'string') return name; // if it's already a component
65
+ function getIcon(name?: unknown): typeof LayoutDashboard {
66
+ if (!name) return depth === 0 ? Settings : Circle;
67
+ if (typeof name !== 'string') return name as typeof LayoutDashboard;
48
68
  return iconMap[name] ?? Settings;
49
69
  }
50
70
 
@@ -54,95 +74,91 @@
54
74
 
55
75
  function isActive(itemHref: string | undefined): boolean {
56
76
  if (!itemHref) return false;
57
- if (itemHref === '/') return currentPath === '/';
58
- 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] === '/');
59
81
  }
60
82
 
61
- /** Check if any child (recursively) is active — used to auto-open parent groups */
62
83
  function hasActiveChild(menuItem: MenuItem): boolean {
63
84
  if (isActive(menuItem.href)) return true;
64
85
  return menuItem.children?.some((c: MenuItem) => hasActiveChild(c)) ?? false;
65
86
  }
66
87
 
67
- function getItemBadge(menuItem: MenuItem): string | undefined {
68
- const meta = menuItem.meta as (MenuItem['meta'] & { badge?: unknown }) | undefined;
69
- return typeof meta?.badge === 'string' ? meta.badge : undefined;
70
- }
71
-
72
- function aggregateBadges(menuItems: MenuItem[] | undefined): string | undefined {
73
- const total = menuItems?.reduce((sum, menuItem) => {
74
- const ownBadge = Number(getItemBadge(menuItem));
75
- const childBadge = Number(aggregateBadges(menuItem.children));
76
- return sum + (Number.isFinite(ownBadge) ? ownBadge : 0) + (Number.isFinite(childBadge) ? childBadge : 0);
77
- }, 0) ?? 0;
78
-
79
- return total > 0 ? String(total) : undefined;
80
- }
81
-
82
88
  const hasChildren = $derived(item.children && item.children.length > 0);
83
89
  const active = $derived(isActive(item.href));
84
90
  const childActive = $derived(hasActiveChild(item));
85
91
  const isExternal = $derived(item.target === '_blank' || item.href?.startsWith('http'));
86
- const badge = $derived(getItemBadge(item));
87
- const childrenBadge = $derived(aggregateBadges(item.children));
88
92
 
89
93
  const finalHref = $derived(isExternal ? item.href : (item.href ? formatLink(item.href) : undefined));
90
- // Auto-open the active branch once, then respect manual collapse/expand.
91
- let isOpen = $state(false);
92
- let touchedOpen = $state(false);
93
-
94
- function setOpen(nextOpen: boolean) {
95
- touchedOpen = true;
96
- isOpen = nextOpen;
97
- }
98
94
 
99
- $effect(() => {
100
- if (childActive && !touchedOpen && !isOpen) isOpen = true;
101
- });
95
+ let isOpen = $state(false);
96
+ $effect(() => { if (childActive) isOpen = true; });
102
97
 
103
98
  const Icon = $derived(getIcon(item.icon));
104
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);
105
104
  </script>
106
105
 
107
106
  {#if item.meta?.hidden}
108
- <!-- hidden item, render nothing -->
107
+ <!-- hidden -->
109
108
  {:else if hasChildren && !collapsed}
110
- <!-- Parent node with children: render as collapsible group -->
111
- <Collapsible.Root open={isOpen} onOpenChange={setOpen}>
112
- <Collapsible.Trigger
113
- class="flex w-full items-center justify-between rounded-xl px-3 {depth === 1 ? 'py-1.5' : 'py-2'} text-sm font-medium transition-all duration-200
114
- {childActive
115
- ? 'text-primary font-semibold'
116
- : 'text-sidebar-foreground/68 hover:bg-sidebar-accent/45 hover:text-sidebar-foreground'}"
117
- style="padding-left: {depth === 0 ? 12 : 4 + (depth - 1) * 12}px"
118
- data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
119
- data-active={childActive ? "true" : "false"}
120
- >
121
- <span class="flex items-center gap-3">
122
- <Icon class="h-4 w-4 flex-shrink-0" />
123
- <span>{label}</span>
124
- </span>
125
- <span class="ml-2 flex items-center gap-2">
126
- {#if childrenBadge}
127
- <span class="rounded-full {childActive ? 'bg-primary/12 text-primary' : 'bg-sidebar-accent text-sidebar-foreground/60'} px-2 py-0.5 text-[10px] font-semibold">{childrenBadge}</span>
128
- {/if}
129
- <ChevronDown class="h-3.5 w-3.5 transition-transform duration-200 {isOpen ? 'rotate-180' : ''}" />
130
- </span>
131
- </Collapsible.Trigger>
132
- <Collapsible.Content>
133
- <div
134
- class="mt-0.5 {depth >= 1 ? 'divide-y divide-border/45' : 'space-y-0'}"
135
- style="padding-left: {depth > 0 ? 0 : 0}px"
136
- data-sidebar="menu-sub"
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"
137
145
  >
138
- {#each item.children as child, _i (_i)}
139
- <SidebarItem item={child} {currentPath} {collapsed} depth={depth + 1} />
140
- {/each}
141
- </div>
142
- </Collapsible.Content>
143
- </Collapsible.Root>
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}
144
161
  {:else if collapsed}
145
- <!-- Collapsed sidebar: icon-only with tooltip -->
146
162
  <Tooltip.Root>
147
163
  <Tooltip.Trigger>
148
164
  {#snippet child({ props }: { props: Record<string, unknown> })}
@@ -151,14 +167,12 @@
151
167
  href={finalHref}
152
168
  target={isExternal ? '_blank' : undefined}
153
169
  rel={isExternal ? 'noopener noreferrer' : undefined}
154
- class="mx-auto flex h-10 w-10 items-center justify-center text-sm font-medium transition-colors duration-200
170
+ class="sidebar-menu-item flex items-center justify-center rounded-md px-2 py-[7px] transition-colors duration-150
155
171
  {active
156
- ? 'text-primary'
157
- : 'text-sidebar-foreground/68 hover:text-sidebar-foreground'}"
158
- data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
159
- data-active={active ? "true" : "false"}
172
+ ? 'sidebar-menu-item-active bg-sidebar-accent text-primary'
173
+ : 'text-sidebar-foreground/50 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
160
174
  >
161
- <Icon class="h-4 w-4 flex-shrink-0" />
175
+ <Icon class="h-[18px] w-[18px] flex-shrink-0" />
162
176
  </a>
163
177
  {/snippet}
164
178
  </Tooltip.Trigger>
@@ -167,26 +181,45 @@
167
181
  </Tooltip.Content>
168
182
  </Tooltip.Root>
169
183
  {:else}
170
- <!-- Leaf node: render as a link -->
171
- <a
172
- href={finalHref}
173
- target={isExternal ? '_blank' : undefined}
174
- rel={isExternal ? 'noopener noreferrer' : undefined}
175
- class="relative flex items-center gap-3 px-3 {depth === 1 ? 'py-1.5' : 'py-2'} text-sm font-medium transition-all duration-200 {depth >= 2 ? 'first:rounded-t-xl last:rounded-b-xl' : 'rounded-xl'}
176
- {active
177
- ? 'text-primary font-semibold'
178
- : 'text-sidebar-foreground/68 hover:bg-sidebar-accent/45 hover:text-sidebar-foreground'}"
179
- style="padding-left: {12 + depth * 12}px"
180
- data-sidebar={depth === 0 ? "menu-button" : "menu-sub-button"}
181
- data-active={active ? "true" : "false"}
182
- >
183
- <Icon class="h-4 w-4 flex-shrink-0" />
184
- <span class="flex-1">{label}</span>
185
- {#if isExternal}
186
- <ExternalLink class="h-3 w-3 opacity-50" />
187
- {/if}
188
- {#if badge}
189
- <span class="rounded-full {active ? 'bg-primary/12 text-primary' : 'bg-sidebar-accent text-sidebar-foreground/60'} px-2 py-0.5 text-[10px] font-semibold">{badge}</span>
190
- {/if}
191
- </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}
192
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, {}, "">;