@svadmin/ui 0.20.3 → 0.21.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.
- package/package.json +1 -1
- package/src/components/AutoTable.svelte +14 -7
- package/src/components/ComboboxField.svelte +5 -0
- package/src/components/DraggableGrid.svelte +1 -7
- package/src/components/FieldRenderer.svelte +8 -1
- package/src/components/PermissionMatrix.svelte +1 -6
- package/src/components/Sidebar.svelte +6 -5
- package/src/components/TaskQueueDrawer.svelte +1 -15
- package/src/components/TenantSwitcher.svelte +1 -9
- package/src/index.ts +4 -4
- package/src/types.ts +33 -0
package/package.json
CHANGED
|
@@ -46,8 +46,10 @@
|
|
|
46
46
|
selectable?: boolean;
|
|
47
47
|
/** Custom header actions (right side) */
|
|
48
48
|
headerActions?: Snippet;
|
|
49
|
-
/** Custom cell
|
|
50
|
-
|
|
49
|
+
/** Custom cell renderers by field key */
|
|
50
|
+
columns?: Record<string, Snippet<[{ value: unknown; record: Record<string, unknown> }]>>;
|
|
51
|
+
/** Global fallback cell renderer */
|
|
52
|
+
defaultCellRenderer?: Snippet<[{ field: FieldDefinition; value: unknown; record: Record<string, unknown> }]>;
|
|
51
53
|
/** Custom row actions (edit/delete column) */
|
|
52
54
|
rowActions?: Snippet<[{ record: Record<string, unknown>; id: string | number }]>;
|
|
53
55
|
/** Custom empty state */
|
|
@@ -64,7 +66,8 @@
|
|
|
64
66
|
resourceName,
|
|
65
67
|
selectable = true,
|
|
66
68
|
headerActions,
|
|
67
|
-
|
|
69
|
+
columns: customColumns,
|
|
70
|
+
defaultCellRenderer,
|
|
68
71
|
rowActions,
|
|
69
72
|
emptyState,
|
|
70
73
|
expandedRowRender,
|
|
@@ -560,8 +563,10 @@
|
|
|
560
563
|
</div>
|
|
561
564
|
{:else}
|
|
562
565
|
{@const field = visibleFields.find(f => f.key === cell.column.id)}
|
|
563
|
-
{#if
|
|
564
|
-
{@render
|
|
566
|
+
{#if customColumns && field && customColumns[field.key]}
|
|
567
|
+
{@render customColumns[field.key]({ value: cell.getValue(), record })}
|
|
568
|
+
{:else if defaultCellRenderer && field}
|
|
569
|
+
{@render defaultCellRenderer({ field, value: cell.getValue(), record })}
|
|
565
570
|
{:else if field?.type === 'boolean'}
|
|
566
571
|
<span class="inline-block h-2 w-2 rounded-full {cell.getValue() ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
|
|
567
572
|
{:else if field?.type === 'date' && cell.getValue()}
|
|
@@ -694,8 +699,10 @@
|
|
|
694
699
|
<div class="flex items-start justify-between gap-4">
|
|
695
700
|
<span class="text-xs font-medium text-muted-foreground shrink-0">{field.label}</span>
|
|
696
701
|
<span class="text-sm text-right truncate max-w-[60%]">
|
|
697
|
-
{#if
|
|
698
|
-
{@render
|
|
702
|
+
{#if customColumns && field && customColumns[field.key]}
|
|
703
|
+
{@render customColumns[field.key]({ value, record })}
|
|
704
|
+
{:else if defaultCellRenderer && field}
|
|
705
|
+
{@render defaultCellRenderer({ field, value, record })}
|
|
699
706
|
{:else if field.type === 'boolean'}
|
|
700
707
|
<span class="inline-block h-2 w-2 rounded-full {value ? 'bg-success' : 'bg-muted-foreground/30'}"></span>
|
|
701
708
|
{:else if field.type === 'date' && value}
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
placeholder?: string;
|
|
19
19
|
searchable?: boolean;
|
|
20
20
|
onSearch?: (value: string) => Filter[];
|
|
21
|
+
id?: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
[key: string]: any;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
let {
|
|
@@ -29,6 +32,7 @@
|
|
|
29
32
|
placeholder = 'Select...',
|
|
30
33
|
searchable = true,
|
|
31
34
|
onSearch,
|
|
35
|
+
...restProps
|
|
32
36
|
}: Props = $props();
|
|
33
37
|
|
|
34
38
|
const { options, query, onSearchChange } = useSelect({
|
|
@@ -71,6 +75,7 @@
|
|
|
71
75
|
type="button"
|
|
72
76
|
class="w-full justify-between font-normal"
|
|
73
77
|
onclick={() => { open = !open; }}
|
|
78
|
+
{...restProps}
|
|
74
79
|
>
|
|
75
80
|
{#if selectedLabel}
|
|
76
81
|
<span class="truncate">{selectedLabel}</span>
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
<script context="module">
|
|
2
|
-
export interface GridModule {
|
|
3
|
-
id: string;
|
|
4
|
-
[key: string]: unknown;
|
|
5
|
-
}
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
1
|
<script lang="ts">
|
|
9
2
|
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { GridModule } from '../types.js';
|
|
10
4
|
|
|
11
5
|
let {
|
|
12
6
|
modules = $bindable<GridModule[]>([]),
|
|
@@ -176,10 +176,11 @@
|
|
|
176
176
|
/>
|
|
177
177
|
{:then editorMod}
|
|
178
178
|
<editorMod.Editor
|
|
179
|
+
id={field.key}
|
|
179
180
|
value={strVal}
|
|
180
181
|
placeholder={t('field.enterValue', { label: field.label })}
|
|
181
182
|
preset="full"
|
|
182
|
-
onchange={(html) => onchange(html)}
|
|
183
|
+
onchange={(html: string) => onchange(html)}
|
|
183
184
|
/>
|
|
184
185
|
{:catch}
|
|
185
186
|
<Textarea
|
|
@@ -206,6 +207,7 @@
|
|
|
206
207
|
|
|
207
208
|
{:else if field.type === 'relation' && field.resource}
|
|
208
209
|
<ComboboxField
|
|
210
|
+
id={field.key}
|
|
209
211
|
resource={field.resource}
|
|
210
212
|
value={value as string | number | null}
|
|
211
213
|
onchange={(v) => onchange(v)}
|
|
@@ -218,6 +220,7 @@
|
|
|
218
220
|
{#if (field.options?.length ?? 0) > 8}
|
|
219
221
|
<!-- Many options → use ComboboxField-style search -->
|
|
220
222
|
<ComboboxField
|
|
223
|
+
id={field.key}
|
|
221
224
|
resource=""
|
|
222
225
|
value={value as string | number | null}
|
|
223
226
|
onchange={(v) => onchange(v)}
|
|
@@ -242,6 +245,7 @@
|
|
|
242
245
|
{#each field.options ?? [] as opt}
|
|
243
246
|
<label class="flex items-center gap-2 text-sm cursor-pointer hover:bg-muted/50 rounded px-1 py-0.5 transition-colors">
|
|
244
247
|
<Checkbox
|
|
248
|
+
id={`${field.key}-${opt.value}`}
|
|
245
249
|
checked={multiVal.includes(opt.value)}
|
|
246
250
|
onCheckedChange={() => toggleMulti(opt.value)}
|
|
247
251
|
/>
|
|
@@ -267,6 +271,7 @@
|
|
|
267
271
|
{:else if field.type === 'boolean'}
|
|
268
272
|
<div class="flex items-center gap-2 pt-1">
|
|
269
273
|
<Switch
|
|
274
|
+
id={field.key}
|
|
270
275
|
checked={boolVal}
|
|
271
276
|
onCheckedChange={(v) => onchange(v)}
|
|
272
277
|
/>
|
|
@@ -289,6 +294,7 @@
|
|
|
289
294
|
{/each}
|
|
290
295
|
</div>
|
|
291
296
|
<Input
|
|
297
|
+
id={field.key}
|
|
292
298
|
type="text"
|
|
293
299
|
placeholder={t('field.tagsPlaceholder')}
|
|
294
300
|
onkeydown={handleTagKeydown}
|
|
@@ -309,6 +315,7 @@
|
|
|
309
315
|
{#each imagesVal as url, i}
|
|
310
316
|
<div class="flex items-center gap-2">
|
|
311
317
|
<Input
|
|
318
|
+
id={i === 0 ? field.key : undefined}
|
|
312
319
|
type="text"
|
|
313
320
|
value={url}
|
|
314
321
|
oninput={(e) => updateImage(i, (e.target as HTMLInputElement).value)}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
<script context="module">
|
|
2
|
-
export type RoleInfo = { code: string; name: string; [key: string]: any };
|
|
3
|
-
export type ResourceInfo = { code: string; name: string; section?: string; [key: string]: any };
|
|
4
|
-
export type ActionInfo = { code: string; name: string; [key: string]: any };
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
1
|
<script lang="ts">
|
|
8
2
|
import { Shield, AlertCircle, Check } from 'lucide-svelte';
|
|
9
3
|
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
10
5
|
|
|
11
6
|
let {
|
|
12
7
|
// Basic metadata lists
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
ChevronLeft, ChevronDown, LogOut, Sun, Moon, Palette
|
|
18
18
|
} from 'lucide-svelte';
|
|
19
19
|
|
|
20
|
-
let { collapsed, identity, title, onToggle, onLogout, menu }: {
|
|
20
|
+
let { collapsed, identity, title, onToggle, onLogout, menu, legacyHashRouting = false }: {
|
|
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
28
|
} = $props();
|
|
28
29
|
|
|
29
30
|
const resources = getResources();
|
|
@@ -205,7 +206,7 @@
|
|
|
205
206
|
{#each group.items as item}
|
|
206
207
|
{@const active = isActive(item.path)}
|
|
207
208
|
<a
|
|
208
|
-
href={`#${item.path}`}
|
|
209
|
+
href={legacyHashRouting ? `#${item.path}` : item.path}
|
|
209
210
|
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
210
211
|
{active
|
|
211
212
|
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
|
|
@@ -228,7 +229,7 @@
|
|
|
228
229
|
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
229
230
|
<a
|
|
230
231
|
{...props}
|
|
231
|
-
href={`#${item.path}`}
|
|
232
|
+
href={legacyHashRouting ? `#${item.path}` : item.path}
|
|
232
233
|
class="flex items-center justify-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
233
234
|
{active
|
|
234
235
|
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
@@ -244,7 +245,7 @@
|
|
|
244
245
|
</Tooltip.Root>
|
|
245
246
|
{:else}
|
|
246
247
|
<a
|
|
247
|
-
href={`#${item.path}`}
|
|
248
|
+
href={legacyHashRouting ? `#${item.path}` : item.path}
|
|
248
249
|
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
249
250
|
{active
|
|
250
251
|
? 'bg-sidebar-accent text-sidebar-accent-foreground sidebar-nav-active'
|
|
@@ -284,7 +285,7 @@
|
|
|
284
285
|
{#each colorThemes as ct}
|
|
285
286
|
<button
|
|
286
287
|
class="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
|
|
287
|
-
onclick={() => { setColorTheme(ct.id); colorPickerOpen = false; }}
|
|
288
|
+
onclick={() => { setColorTheme(ct.id as typeof ct.id & import('@svadmin/core').ColorTheme); colorPickerOpen = false; }}
|
|
288
289
|
>
|
|
289
290
|
<span
|
|
290
291
|
class="h-3.5 w-3.5 rounded-full {getColorTheme() === ct.id ? 'ring-2 ring-offset-1 scale-110' : 'opacity-70'}"
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
<script context="module">
|
|
2
|
-
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
3
|
-
|
|
4
|
-
export interface BackgroundTask {
|
|
5
|
-
id: string;
|
|
6
|
-
title: string;
|
|
7
|
-
status: TaskStatus;
|
|
8
|
-
progress?: number; // 0-100
|
|
9
|
-
message?: string;
|
|
10
|
-
createdAt: string | Date;
|
|
11
|
-
downloadUrl?: string;
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
</script>
|
|
15
|
-
|
|
16
1
|
<script lang="ts">
|
|
17
2
|
import type { Snippet } from 'svelte';
|
|
18
3
|
import { Loader2, CheckCircle2, XCircle, Clock, Download, X, ListTodo } from 'lucide-svelte';
|
|
19
4
|
import { Button } from './ui/button/index.js';
|
|
20
5
|
import { Badge } from './ui/badge/index.js';
|
|
6
|
+
import type { BackgroundTask, TaskStatus } from '../types.js';
|
|
21
7
|
|
|
22
8
|
let {
|
|
23
9
|
open = $bindable(false),
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
<script context="module">
|
|
2
|
-
export interface Tenant {
|
|
3
|
-
id: string;
|
|
4
|
-
name: string;
|
|
5
|
-
logo?: string;
|
|
6
|
-
[key: string]: unknown;
|
|
7
|
-
}
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
1
|
<script lang="ts">
|
|
11
2
|
import type { Snippet } from 'svelte';
|
|
12
3
|
import { ChevronsUpDown, Building2, Check } from 'lucide-svelte';
|
|
13
4
|
import { Button } from './ui/button/index.js';
|
|
14
5
|
import * as DropdownMenu from './ui/dropdown-menu/index.js';
|
|
6
|
+
import type { Tenant } from '../types.js';
|
|
15
7
|
|
|
16
8
|
let {
|
|
17
9
|
tenants = [],
|
package/src/index.ts
CHANGED
|
@@ -64,15 +64,15 @@ export { default as SettingsPage } from "./components/SettingsPage.svelte";
|
|
|
64
64
|
export { default as AppearanceSettings } from "./components/AppearanceSettings.svelte";
|
|
65
65
|
export { default as AboutSettings } from "./components/AboutSettings.svelte";
|
|
66
66
|
export { default as PermissionMatrix } from "./components/PermissionMatrix.svelte";
|
|
67
|
-
export type { RoleInfo, ResourceInfo, ActionInfo } from "./
|
|
67
|
+
export type { RoleInfo, ResourceInfo, ActionInfo } from "./types.js";
|
|
68
68
|
export { default as RolesSettings } from "./components/RolesSettings.svelte";
|
|
69
69
|
export { default as AuditLogViewer } from "./components/AuditLogViewer.svelte";
|
|
70
70
|
export { default as TenantSwitcher } from "./components/TenantSwitcher.svelte";
|
|
71
|
-
export type { Tenant } from "./
|
|
71
|
+
export type { Tenant } from "./types.js";
|
|
72
72
|
export { default as TaskQueueDrawer } from "./components/TaskQueueDrawer.svelte";
|
|
73
|
-
export type { BackgroundTask, TaskStatus } from "./
|
|
73
|
+
export type { BackgroundTask, TaskStatus } from "./types.js";
|
|
74
74
|
export { default as DraggableGrid } from "./components/DraggableGrid.svelte";
|
|
75
|
-
export type { GridModule } from "./
|
|
75
|
+
export type { GridModule } from "./types.js";
|
|
76
76
|
|
|
77
77
|
// Dashboard Charts (zero-dependency SVG)
|
|
78
78
|
export { BarChart, LineChart, PieChart } from './components/charts/index.js';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type RoleInfo = { code: string; name: string; [key: string]: any };
|
|
2
|
+
export type ResourceInfo = { code: string; name: string; section?: string; [key: string]: any };
|
|
3
|
+
export type ActionInfo = { code: string; name: string; [key: string]: any };
|
|
4
|
+
|
|
5
|
+
export interface Tenant {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
logo?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
13
|
+
|
|
14
|
+
export interface BackgroundTask {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
status: TaskStatus;
|
|
18
|
+
progress?: number; // 0-100
|
|
19
|
+
message?: string;
|
|
20
|
+
createdAt: string | Date;
|
|
21
|
+
downloadUrl?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface GridModule {
|
|
26
|
+
id: string;
|
|
27
|
+
w: number;
|
|
28
|
+
h: number;
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
title?: string;
|
|
32
|
+
componentProps?: any; // Component specific props
|
|
33
|
+
}
|