@svadmin/ui 0.5.14 → 0.7.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 +2 -2
- package/src/components/AuditLogDrawer.svelte +93 -0
- package/src/components/AutoTable.svelte +1 -1
- package/src/components/CreatePage.svelte +2 -6
- package/src/components/EditPage.svelte +9 -29
- package/src/components/EmptyState.svelte +1 -1
- package/src/components/ForgotPasswordPage.svelte +12 -12
- package/src/components/Header.svelte +1 -1
- package/src/components/Layout.svelte +1 -1
- package/src/components/LoginPage.svelte +12 -12
- package/src/components/ProfilePage.svelte +7 -3
- package/src/components/RegisterPage.svelte +11 -11
- package/src/components/ShowPage.svelte +10 -8
- package/src/components/Sidebar.svelte +8 -8
- package/src/components/StatsCard.svelte +1 -1
- package/src/components/buttons/CloneButton.svelte +1 -1
- package/src/components/buttons/CreateButton.svelte +1 -1
- package/src/components/buttons/DeleteButton.svelte +1 -1
- package/src/components/buttons/EditButton.svelte +1 -1
- package/src/components/buttons/ExportButton.svelte +18 -12
- package/src/components/buttons/ImportButton.svelte +18 -12
- package/src/components/buttons/ShowButton.svelte +18 -11
- package/src/components/ui/avatar/avatar.svelte +1 -1
- package/src/components/ui/button/button.svelte +2 -2
- package/src/components/ui/card/card-footer.svelte +1 -1
- package/src/components/ui/card/card.svelte +1 -1
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -1
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +1 -1
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -1
- package/src/components/ui/label/label.svelte +1 -1
- package/src/components/ui/select/select.svelte +1 -1
- package/src/components/ui/switch/switch.svelte +1 -1
- package/src/components/ui/table/table-body.svelte +1 -1
- package/src/components/ui/table/table-cell.svelte +1 -1
- package/src/components/ui/table/table-footer.svelte +1 -1
- package/src/components/ui/table/table-head.svelte +1 -1
- package/src/components/ui/table/table-header.svelte +1 -1
- package/src/components/ui/table/table-row.svelte +1 -1
- package/src/components/ui/table/table.svelte +1 -1
- package/src/components/ui/tabs/tabs-trigger.svelte +1 -1
- package/src/components/ui/textarea/textarea.svelte +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"svelte": "^5.0.0",
|
|
24
24
|
"@svadmin/core": "^0.5.0",
|
|
25
|
-
"@tanstack/svelte-query": "^
|
|
25
|
+
"@tanstack/svelte-query": "^6.0.0",
|
|
26
26
|
"bits-ui": "^2.0.0",
|
|
27
27
|
"tailwind-variants": "^3.0.0",
|
|
28
28
|
"clsx": "^2.0.0",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAuditLogProvider, t } from '@svadmin/core';
|
|
3
|
+
import type { AuditEntry } from '@svadmin/core';
|
|
4
|
+
import * as Sheet from './ui/sheet/index.js';
|
|
5
|
+
import { History, Loader2 } from 'lucide-svelte';
|
|
6
|
+
|
|
7
|
+
let { open = $bindable(false), resource, recordId } = $props<{
|
|
8
|
+
open: boolean;
|
|
9
|
+
resource: string;
|
|
10
|
+
recordId?: string | number;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
let logs = $state<AuditEntry[]>([]);
|
|
14
|
+
let isLoading = $state(false);
|
|
15
|
+
|
|
16
|
+
$effect(() => {
|
|
17
|
+
if (open) {
|
|
18
|
+
const provider = getAuditLogProvider();
|
|
19
|
+
if (provider) {
|
|
20
|
+
isLoading = true;
|
|
21
|
+
provider.get({ resource, meta: recordId ? { recordId } : undefined })
|
|
22
|
+
.then((data) => {
|
|
23
|
+
logs = data;
|
|
24
|
+
isLoading = false;
|
|
25
|
+
})
|
|
26
|
+
.catch((err) => {
|
|
27
|
+
console.error('[svadmin] Failed to fetch audit logs', err);
|
|
28
|
+
isLoading = false;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Action semantic color mapping
|
|
35
|
+
function getActionColor(action: string) {
|
|
36
|
+
switch (action) {
|
|
37
|
+
case 'create': return 'bg-green-500';
|
|
38
|
+
case 'update': return 'bg-blue-500';
|
|
39
|
+
case 'delete': return 'bg-red-500';
|
|
40
|
+
default: return 'bg-slate-400';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<Sheet.Root bind:open>
|
|
46
|
+
<Sheet.Content class="w-full sm:max-w-md overflow-y-auto">
|
|
47
|
+
<Sheet.Header>
|
|
48
|
+
<Sheet.Title class="flex items-center gap-2">
|
|
49
|
+
<History class="h-5 w-5" />
|
|
50
|
+
{t('common.history') || 'History'}
|
|
51
|
+
</Sheet.Title>
|
|
52
|
+
<Sheet.Description>
|
|
53
|
+
Audit log and revision history for this record.
|
|
54
|
+
</Sheet.Description>
|
|
55
|
+
</Sheet.Header>
|
|
56
|
+
|
|
57
|
+
<div class="mt-8 space-y-4">
|
|
58
|
+
{#if isLoading}
|
|
59
|
+
<div class="flex justify-center py-8 text-muted-foreground">
|
|
60
|
+
<Loader2 class="h-6 w-6 animate-spin" />
|
|
61
|
+
</div>
|
|
62
|
+
{:else if logs.length === 0}
|
|
63
|
+
<div class="text-center py-10">
|
|
64
|
+
<History class="h-8 w-8 mx-auto text-muted-foreground/30 mb-3" />
|
|
65
|
+
<p class="text-sm text-muted-foreground">
|
|
66
|
+
{t('common.noData') || 'No audit logs found'}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
{:else}
|
|
70
|
+
<div class="relative border-l border-border ml-3 pl-6 space-y-8">
|
|
71
|
+
{#each logs as log}
|
|
72
|
+
<div class="relative">
|
|
73
|
+
<span class="absolute -left-7 top-1 flex h-2 w-2 items-center justify-center rounded-full ring-4 ring-background {getActionColor(log.action)}"></span>
|
|
74
|
+
<div class="flex flex-col gap-1.5">
|
|
75
|
+
<div class="flex items-center gap-2 justify-between">
|
|
76
|
+
<span class="text-sm font-semibold capitalize">{log.action}</span>
|
|
77
|
+
<span class="text-xs text-muted-foreground">
|
|
78
|
+
{new Date(log.timestamp).toLocaleString()}
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
{#if log.userId}
|
|
82
|
+
<span class="text-xs text-muted-foreground font-mono bg-muted inline-flex px-1.5 py-0.5 rounded w-fit">
|
|
83
|
+
User: {log.userId}
|
|
84
|
+
</span>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
{/each}
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
</Sheet.Content>
|
|
93
|
+
</Sheet.Root>
|
|
@@ -414,7 +414,7 @@
|
|
|
414
414
|
{/if}
|
|
415
415
|
|
|
416
416
|
<!-- Table (TanStack-powered) -->
|
|
417
|
-
<div class="rounded-lg
|
|
417
|
+
<div class="rounded-lg bg-card shadow-sm overflow-hidden">
|
|
418
418
|
{#if query.isLoading}
|
|
419
419
|
<div class="p-4 space-y-3">
|
|
420
420
|
<div class="flex gap-4 mb-2">
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
import type { Snippet } from 'svelte';
|
|
6
6
|
import PageHeader from './PageHeader.svelte';
|
|
7
7
|
import AutoForm from './AutoForm.svelte';
|
|
8
|
-
import
|
|
9
|
-
import { Button } from './ui/button/index.js';
|
|
10
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
8
|
+
import ListButton from './buttons/ListButton.svelte';
|
|
11
9
|
|
|
12
10
|
interface Props {
|
|
13
11
|
resourceName: string;
|
|
@@ -30,9 +28,7 @@
|
|
|
30
28
|
<div class="space-y-6 {className}">
|
|
31
29
|
<PageHeader title={pageTitle}>
|
|
32
30
|
{#snippet actions()}
|
|
33
|
-
<
|
|
34
|
-
<ArrowLeft class="h-5 w-5" />
|
|
35
|
-
</TooltipButton>
|
|
31
|
+
<ListButton resource={resourceName} hideText />
|
|
36
32
|
{#if headerActions}
|
|
37
33
|
{@render headerActions()}
|
|
38
34
|
{/if}
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
import type { Snippet } from 'svelte';
|
|
6
6
|
import PageHeader from './PageHeader.svelte';
|
|
7
7
|
import AutoForm from './AutoForm.svelte';
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
8
|
+
import ListButton from './buttons/ListButton.svelte';
|
|
9
|
+
import ShowButton from './buttons/ShowButton.svelte';
|
|
10
|
+
import RefreshButton from './buttons/RefreshButton.svelte';
|
|
11
|
+
import DeleteButton from './buttons/DeleteButton.svelte';
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
resourceName: string;
|
|
@@ -31,29 +31,16 @@
|
|
|
31
31
|
const resource = $derived(getResource(resourceName));
|
|
32
32
|
const pageTitle = $derived(title ?? `${t('common.edit')} ${resource.label} #${id}`);
|
|
33
33
|
const showDelete = $derived(canDelete ?? resource.canDelete !== false);
|
|
34
|
-
|
|
35
|
-
const deleteResult = useDelete({ get resource() { return resourceName; } });
|
|
36
|
-
const deleteMutation = deleteResult.mutation;
|
|
37
|
-
|
|
38
|
-
let confirmOpen = $state(false);
|
|
39
|
-
|
|
40
|
-
async function handleDelete() {
|
|
41
|
-
await deleteMutation.mutateAsync({ id, resource: resourceName });
|
|
42
|
-
confirmOpen = false;
|
|
43
|
-
navigate(`/${resourceName}`);
|
|
44
|
-
}
|
|
45
34
|
</script>
|
|
46
35
|
|
|
47
36
|
<div class="space-y-6 {className}">
|
|
48
37
|
<PageHeader title={pageTitle}>
|
|
49
38
|
{#snippet actions()}
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{#if showDelete}
|
|
54
|
-
<
|
|
55
|
-
<Trash2 class="h-4 w-4" />
|
|
56
|
-
</TooltipButton>
|
|
39
|
+
<ListButton resource={resourceName} hideText />
|
|
40
|
+
<ShowButton resource={resourceName} recordItemId={id} hideText />
|
|
41
|
+
<RefreshButton resource={resourceName} hideText />
|
|
42
|
+
{#if showDelete !== false}
|
|
43
|
+
<DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => navigate(`/${resourceName}`)} />
|
|
57
44
|
{/if}
|
|
58
45
|
{#if headerActions}
|
|
59
46
|
{@render headerActions()}
|
|
@@ -63,10 +50,3 @@
|
|
|
63
50
|
|
|
64
51
|
<AutoForm {resourceName} mode="edit" {id} />
|
|
65
52
|
</div>
|
|
66
|
-
|
|
67
|
-
<ConfirmDialog
|
|
68
|
-
bind:open={confirmOpen}
|
|
69
|
-
title={t('common.deleteConfirm')}
|
|
70
|
-
onconfirm={handleDelete}
|
|
71
|
-
oncancel={() => confirmOpen = false}
|
|
72
|
-
/>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
17
|
<div
|
|
18
|
-
class="flex flex-col items-center justify-center p-8 text-center min-h-[300px] bg-card rounded-lg
|
|
18
|
+
class="flex flex-col items-center justify-center p-8 text-center min-h-[300px] bg-card/50 rounded-lg shadow-sm"
|
|
19
19
|
in:scale={{ start: 0.95, duration: 250, opacity: 0 }}
|
|
20
20
|
>
|
|
21
21
|
<div class="h-20 w-20 rounded-full bg-muted flex items-center justify-center mb-4">
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Label } from './ui/label/index.js';
|
|
8
8
|
import * as Card from './ui/card/index.js';
|
|
9
9
|
import * as Alert from './ui/alert/index.js';
|
|
10
|
-
import { KeyRound,
|
|
10
|
+
import { KeyRound, User, ArrowLeft, CheckCircle, Loader2, AlertCircle } from 'lucide-svelte';
|
|
11
11
|
|
|
12
12
|
let { title = 'Admin' } = $props<{
|
|
13
13
|
title?: string;
|
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
|
|
16
16
|
const forgot = useForgotPassword();
|
|
17
17
|
|
|
18
|
-
let
|
|
18
|
+
let identifier = $state('');
|
|
19
19
|
let error = $state('');
|
|
20
20
|
let sent = $state(false);
|
|
21
21
|
|
|
22
|
-
async function handleSubmit(e:
|
|
22
|
+
async function handleSubmit(e: Event) {
|
|
23
23
|
e.preventDefault();
|
|
24
24
|
error = '';
|
|
25
25
|
|
|
26
|
-
if (!
|
|
26
|
+
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
27
27
|
|
|
28
|
-
const result = await forgot.mutate({ email });
|
|
28
|
+
const result = await forgot.mutate({ email: identifier, username: identifier });
|
|
29
29
|
if (result.success) {
|
|
30
30
|
sent = true;
|
|
31
31
|
} else {
|
|
@@ -73,16 +73,16 @@
|
|
|
73
73
|
{/if}
|
|
74
74
|
|
|
75
75
|
<div class="space-y-2">
|
|
76
|
-
<Label for="forgot-
|
|
76
|
+
<Label for="forgot-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
77
77
|
<div class="relative">
|
|
78
|
-
<
|
|
78
|
+
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
79
79
|
<Input
|
|
80
|
-
id="forgot-
|
|
81
|
-
type="
|
|
82
|
-
placeholder=
|
|
83
|
-
bind:value={
|
|
80
|
+
id="forgot-identifier"
|
|
81
|
+
type="text"
|
|
82
|
+
placeholder={t('auth.identifierPlaceholder')}
|
|
83
|
+
bind:value={identifier}
|
|
84
84
|
class="pl-9"
|
|
85
|
-
autocomplete="
|
|
85
|
+
autocomplete="username"
|
|
86
86
|
/>
|
|
87
87
|
</div>
|
|
88
88
|
</div>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}>();
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
|
-
<header class="sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between
|
|
24
|
+
<header class="sticky top-0 z-30 flex h-14 w-full shrink-0 items-center justify-between bg-white/60 dark:bg-slate-900/60 backdrop-blur-md px-4 md:px-6">
|
|
25
25
|
<div class="flex items-center gap-4">
|
|
26
26
|
{#if children}
|
|
27
27
|
{@render children()}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
<div role="application" onkeydown={(e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); commandOpen = true; } }}>
|
|
65
65
|
{#if loading}
|
|
66
66
|
<div class="flex h-screen" in:fade={{ duration: 150 }}>
|
|
67
|
-
<div class="hidden md:block w-64
|
|
67
|
+
<div class="hidden md:block w-64 bg-sidebar/80 p-4 space-y-4">
|
|
68
68
|
<Skeleton class="h-8 w-32" />
|
|
69
69
|
<div class="space-y-2 mt-6">
|
|
70
70
|
{#each Array(5) as _}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as Alert from './ui/alert/index.js';
|
|
10
10
|
import PasswordInput from './PasswordInput.svelte';
|
|
11
11
|
import { Separator } from './ui/separator/index.js';
|
|
12
|
-
import { LogIn,
|
|
12
|
+
import { LogIn, User, Loader2, AlertCircle } from 'lucide-svelte';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Social provider config for the login page.
|
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
const login = useLogin();
|
|
42
42
|
const authProvider = getAuthProvider();
|
|
43
43
|
|
|
44
|
-
let
|
|
44
|
+
let identifier = $state('');
|
|
45
45
|
let password = $state('');
|
|
46
46
|
let error = $state('');
|
|
47
47
|
|
|
48
|
-
async function handleSubmit(e:
|
|
48
|
+
async function handleSubmit(e: Event) {
|
|
49
49
|
e.preventDefault();
|
|
50
50
|
error = '';
|
|
51
51
|
|
|
52
|
-
if (!
|
|
52
|
+
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
53
53
|
if (!password) { error = t('auth.passwordRequired'); return; }
|
|
54
54
|
|
|
55
|
-
const result = await login.mutate({ email, password });
|
|
55
|
+
const result = await login.mutate({ email: identifier, username: identifier, password });
|
|
56
56
|
if (result.success) {
|
|
57
57
|
onSuccess?.();
|
|
58
58
|
} else {
|
|
@@ -81,16 +81,16 @@
|
|
|
81
81
|
{/if}
|
|
82
82
|
|
|
83
83
|
<div class="space-y-2">
|
|
84
|
-
<Label for="login-
|
|
84
|
+
<Label for="login-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
85
85
|
<div class="relative">
|
|
86
|
-
<
|
|
86
|
+
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
87
87
|
<Input
|
|
88
|
-
id="login-
|
|
89
|
-
type="
|
|
90
|
-
placeholder=
|
|
91
|
-
bind:value={
|
|
88
|
+
id="login-identifier"
|
|
89
|
+
type="text"
|
|
90
|
+
placeholder={t('auth.identifierPlaceholder')}
|
|
91
|
+
bind:value={identifier}
|
|
92
92
|
class="pl-9"
|
|
93
|
-
autocomplete="
|
|
93
|
+
autocomplete="username"
|
|
94
94
|
/>
|
|
95
95
|
</div>
|
|
96
96
|
</div>
|
|
@@ -88,10 +88,14 @@
|
|
|
88
88
|
<p class="text-lg font-semibold truncate">{identity.data.name ?? '—'}</p>
|
|
89
89
|
</div>
|
|
90
90
|
|
|
91
|
-
{#if identity.data.email}
|
|
91
|
+
{#if identity.data.email || identity.data.username}
|
|
92
92
|
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
{#if identity.data.email}
|
|
94
|
+
<Mail class="h-4 w-4 shrink-0" />
|
|
95
|
+
{:else}
|
|
96
|
+
<User class="h-4 w-4 shrink-0" />
|
|
97
|
+
{/if}
|
|
98
|
+
<span class="truncate">{identity.data.email || identity.data.username}</span>
|
|
95
99
|
</div>
|
|
96
100
|
{/if}
|
|
97
101
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import * as Card from './ui/card/index.js';
|
|
9
9
|
import * as Alert from './ui/alert/index.js';
|
|
10
10
|
import PasswordInput from './PasswordInput.svelte';
|
|
11
|
-
import { UserPlus,
|
|
11
|
+
import { UserPlus, User, Loader2, AlertCircle } from 'lucide-svelte';
|
|
12
12
|
|
|
13
13
|
let { title = 'Admin', onSuccess } = $props<{
|
|
14
14
|
title?: string;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const register = useRegister();
|
|
19
19
|
|
|
20
|
-
let
|
|
20
|
+
let identifier = $state('');
|
|
21
21
|
let password = $state('');
|
|
22
22
|
let confirmPassword = $state('');
|
|
23
23
|
let error = $state('');
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
e.preventDefault();
|
|
27
27
|
error = '';
|
|
28
28
|
|
|
29
|
-
if (!
|
|
29
|
+
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
30
30
|
if (!password) { error = t('auth.passwordRequired'); return; }
|
|
31
31
|
if (password !== confirmPassword) { error = t('auth.passwordMismatch'); return; }
|
|
32
32
|
|
|
33
|
-
const result = await register.mutate({ email, password });
|
|
33
|
+
const result = await register.mutate({ email: identifier, username: identifier, password });
|
|
34
34
|
if (result.success) {
|
|
35
35
|
onSuccess?.();
|
|
36
36
|
} else {
|
|
@@ -59,16 +59,16 @@
|
|
|
59
59
|
{/if}
|
|
60
60
|
|
|
61
61
|
<div class="space-y-2">
|
|
62
|
-
<Label for="register-
|
|
62
|
+
<Label for="register-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
63
63
|
<div class="relative">
|
|
64
|
-
<
|
|
64
|
+
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
65
65
|
<Input
|
|
66
|
-
id="register-
|
|
67
|
-
type="
|
|
68
|
-
placeholder=
|
|
69
|
-
bind:value={
|
|
66
|
+
id="register-identifier"
|
|
67
|
+
type="text"
|
|
68
|
+
placeholder={t('auth.identifierPlaceholder')}
|
|
69
|
+
bind:value={identifier}
|
|
70
70
|
class="pl-9"
|
|
71
|
-
autocomplete="
|
|
71
|
+
autocomplete="username"
|
|
72
72
|
/>
|
|
73
73
|
</div>
|
|
74
74
|
</div>
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
import * as Card from './ui/card/index.js';
|
|
8
8
|
import { Skeleton } from './ui/skeleton/index.js';
|
|
9
9
|
import PageHeader from './PageHeader.svelte';
|
|
10
|
-
import TooltipButton from './TooltipButton.svelte';
|
|
11
10
|
import { getDisplayComponent } from './fieldComponentMap';
|
|
12
|
-
import
|
|
11
|
+
import ListButton from './buttons/ListButton.svelte';
|
|
12
|
+
import EditButton from './buttons/EditButton.svelte';
|
|
13
|
+
import DeleteButton from './buttons/DeleteButton.svelte';
|
|
14
|
+
import RefreshButton from './buttons/RefreshButton.svelte';
|
|
13
15
|
|
|
14
16
|
let { resourceName, id } = $props<{ resourceName: string; id: string | number }>();
|
|
15
17
|
|
|
@@ -22,14 +24,14 @@
|
|
|
22
24
|
<div class="space-y-6">
|
|
23
25
|
<PageHeader title="{resource.label} {t('common.detail')}">
|
|
24
26
|
{#snippet actions()}
|
|
25
|
-
<
|
|
26
|
-
<ArrowLeft class="h-5 w-5" />
|
|
27
|
-
</TooltipButton>
|
|
27
|
+
<ListButton resource={resourceName} hideText />
|
|
28
28
|
{#if resource.canEdit !== false}
|
|
29
|
-
<
|
|
30
|
-
<Pencil class="h-4 w-4" data-icon="inline-start" /> {t('common.edit')}
|
|
31
|
-
</Button>
|
|
29
|
+
<EditButton resource={resourceName} recordItemId={id} hideText />
|
|
32
30
|
{/if}
|
|
31
|
+
{#if resource.canDelete !== false}
|
|
32
|
+
<DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => navigate(`/${resourceName}`)} />
|
|
33
|
+
{/if}
|
|
34
|
+
<RefreshButton resource={resourceName} hideText />
|
|
33
35
|
{/snippet}
|
|
34
36
|
</PageHeader>
|
|
35
37
|
|
|
@@ -142,13 +142,13 @@
|
|
|
142
142
|
|
|
143
143
|
<aside
|
|
144
144
|
aria-label="Sidebar navigation"
|
|
145
|
-
class="fixed inset-y-0 left-0 z-30 flex flex-col backdrop-blur-xl
|
|
145
|
+
class="fixed inset-y-0 left-0 z-30 flex flex-col backdrop-blur-xl transition-all duration-300"
|
|
146
146
|
style="background-color: var(--sidebar);"
|
|
147
147
|
class:w-64={!collapsed}
|
|
148
148
|
class:w-16={collapsed}
|
|
149
149
|
>
|
|
150
150
|
<!-- Logo -->
|
|
151
|
-
<div class="flex h-16 items-center justify-between
|
|
151
|
+
<div class="flex h-16 items-center justify-between px-4">
|
|
152
152
|
{#if !collapsed}
|
|
153
153
|
<div class="flex items-center gap-3">
|
|
154
154
|
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
|
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
{#if collapsed}
|
|
209
209
|
<Tooltip.Root>
|
|
210
210
|
<Tooltip.Trigger>
|
|
211
|
-
{#snippet child({ props })}
|
|
211
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
212
212
|
<a
|
|
213
213
|
{...props}
|
|
214
214
|
href={`#${item.path}`}
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
</ScrollArea>
|
|
246
246
|
|
|
247
247
|
<!-- Footer -->
|
|
248
|
-
<div
|
|
248
|
+
<div>
|
|
249
249
|
<!-- Color theme picker -->
|
|
250
250
|
{#if !collapsed}
|
|
251
251
|
<div class="px-3 pt-3 pb-1">
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
/>
|
|
295
295
|
<div class="flex-1 min-w-0">
|
|
296
296
|
<p class="truncate text-sm font-medium text-sidebar-foreground">{identity.name}</p>
|
|
297
|
-
<p class="truncate text-[10px] text-sidebar-foreground/50">
|
|
297
|
+
<p class="truncate text-[10px] text-sidebar-foreground/50">{((identity as Record<string, unknown>).role || (identity as Record<string, unknown>).roleName) ?? 'User'}</p>
|
|
298
298
|
</div>
|
|
299
299
|
</div>
|
|
300
300
|
<div class="flex items-center justify-between px-1 mt-1">
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
<div class="px-1 py-3 space-y-1">
|
|
318
318
|
<Tooltip.Root>
|
|
319
319
|
<Tooltip.Trigger>
|
|
320
|
-
{#snippet child({ props })}
|
|
320
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
321
321
|
<Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class="w-full text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
322
322
|
<span class="text-xs font-bold">{localeLabel}</span>
|
|
323
323
|
</Button>
|
|
@@ -327,7 +327,7 @@
|
|
|
327
327
|
</Tooltip.Root>
|
|
328
328
|
<Tooltip.Root>
|
|
329
329
|
<Tooltip.Trigger>
|
|
330
|
-
{#snippet child({ props })}
|
|
330
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
331
331
|
<Button {...props} variant="ghost" size="icon" onclick={toggleTheme} class="w-full text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
332
332
|
{#if getResolvedTheme() === 'dark'}
|
|
333
333
|
<Sun class="h-4 w-4" />
|
|
@@ -341,7 +341,7 @@
|
|
|
341
341
|
</Tooltip.Root>
|
|
342
342
|
<Tooltip.Root>
|
|
343
343
|
<Tooltip.Trigger>
|
|
344
|
-
{#snippet child({ props })}
|
|
344
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
345
345
|
<Button {...props} variant="ghost" size="icon" onclick={onLogout} class="w-full text-sidebar-foreground/60 hover:text-destructive">
|
|
346
346
|
<LogOut class="h-4 w-4" />
|
|
347
347
|
</Button>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const variantMap: Record<StyleVariant, string> = {
|
|
17
|
-
default: '
|
|
17
|
+
default: 'bg-card shadow-sm hover:shadow-md transition-shadow duration-300',
|
|
18
18
|
outline: 'border-2 border-border bg-transparent',
|
|
19
19
|
filled: 'border-0 bg-muted/50 shadow-sm',
|
|
20
20
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Button } from '../ui/button/index.js';
|
|
4
4
|
import { Copy } from 'lucide-svelte';
|
|
5
5
|
|
|
6
|
-
let { resource, recordItemId, hideText = false, accessControl, class: className = '' } = $props<{
|
|
6
|
+
let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
recordItemId: string | number;
|
|
9
9
|
hideText?: boolean;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Button } from '../ui/button/index.js';
|
|
4
4
|
import { Plus } from 'lucide-svelte';
|
|
5
5
|
|
|
6
|
-
let { resource, hideText = false, accessControl, class: className = '' } = $props<{
|
|
6
|
+
let { resource, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
hideText?: boolean;
|
|
9
9
|
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
resource, recordItemId, hideText = false,
|
|
8
|
-
accessControl, onSuccess, undoable = false, class: className = '',
|
|
8
|
+
accessControl = { enabled: true, hideIfUnauthorized: true }, onSuccess, undoable = false, class: className = '',
|
|
9
9
|
} = $props<{
|
|
10
10
|
resource: string;
|
|
11
11
|
recordItemId: string | number;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Button } from '../ui/button/index.js';
|
|
4
4
|
import { Pencil } from 'lucide-svelte';
|
|
5
5
|
|
|
6
|
-
let { resource, recordItemId, hideText = false, accessControl, class: className = '' } = $props<{
|
|
6
|
+
let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
recordItemId: string | number;
|
|
9
9
|
hideText?: boolean;
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useExport, t } from '@svadmin/core';
|
|
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
5
|
|
|
6
|
-
let { resource, hideText = false, class: className = '' } = $props<{
|
|
6
|
+
let { resource, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
hideText?: boolean;
|
|
9
|
+
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
9
10
|
class?: string;
|
|
10
11
|
}>();
|
|
11
12
|
|
|
12
13
|
const { triggerExport, isLoading } = useExport({ get resource() { return resource; } });
|
|
14
|
+
const can = $derived(accessControl?.enabled ? useCan(resource, 'export') : null);
|
|
15
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
|
|
13
16
|
</script>
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
{#if !hidden}
|
|
19
|
+
|
|
20
|
+
<Button
|
|
21
|
+
variant="outline"
|
|
22
|
+
size={hideText ? 'icon' : 'sm'}
|
|
23
|
+
class={className}
|
|
24
|
+
disabled={isLoading || (can ? !can.allowed : false)}
|
|
25
|
+
onclick={triggerExport}
|
|
26
|
+
>
|
|
27
|
+
<Download class="h-4 w-4" />
|
|
28
|
+
{#if !hideText}<span class="ml-1">{t('common.export')}</span>{/if}
|
|
29
|
+
</Button>
|
|
30
|
+
{/if}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useImport, t, type UseImportOptions } from '@svadmin/core';
|
|
2
|
+
import { useImport, useCan, t, type UseImportOptions } from '@svadmin/core';
|
|
3
3
|
import { Button } from '../ui/button/index.js';
|
|
4
4
|
import { Upload } from 'lucide-svelte';
|
|
5
5
|
|
|
6
|
-
let { resource, hideText = false, onFinish, class: className = '' } = $props<{
|
|
6
|
+
let { resource, hideText = false, onFinish, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
hideText?: boolean;
|
|
9
9
|
onFinish?: (result: { succeeded: unknown[]; errored: { request: unknown; error: unknown }[] }) => void;
|
|
10
|
+
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
10
11
|
class?: string;
|
|
11
12
|
}>();
|
|
12
13
|
|
|
@@ -29,8 +30,12 @@
|
|
|
29
30
|
input.value = ''; // reset for re-import
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
|
|
34
|
+
const can = $derived(accessControl?.enabled ? useCan(resource, 'import') : null);
|
|
35
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
|
|
32
36
|
</script>
|
|
33
37
|
|
|
38
|
+
{#if !hidden}
|
|
34
39
|
<input
|
|
35
40
|
bind:this={fileInput}
|
|
36
41
|
type="file"
|
|
@@ -38,13 +43,14 @@
|
|
|
38
43
|
style="display:none"
|
|
39
44
|
onchange={handleFileChange}
|
|
40
45
|
/>
|
|
41
|
-
<Button
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</Button>
|
|
46
|
+
<Button
|
|
47
|
+
variant="outline"
|
|
48
|
+
size={hideText ? 'icon' : 'sm'}
|
|
49
|
+
class={className}
|
|
50
|
+
disabled={importHook.isLoading || (can ? !can.allowed : false)}
|
|
51
|
+
onclick={triggerImport}
|
|
52
|
+
>
|
|
53
|
+
<Upload class="h-4 w-4" />
|
|
54
|
+
{#if !hideText}<span class="ml-1">{t('common.import')}</span>{/if}
|
|
55
|
+
</Button>
|
|
56
|
+
{/if}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useNavigation, t } from '@svadmin/core';
|
|
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
5
|
|
|
6
|
-
let { resource, recordItemId, hideText = false, class: className = '' } = $props<{
|
|
6
|
+
let { resource, recordItemId, hideText = false, accessControl = { enabled: true, hideIfUnauthorized: true }, class: className = '' } = $props<{
|
|
7
7
|
resource: string;
|
|
8
8
|
recordItemId: string | number;
|
|
9
9
|
hideText?: boolean;
|
|
10
|
+
accessControl?: { enabled?: boolean; hideIfUnauthorized?: boolean };
|
|
10
11
|
class?: string;
|
|
11
12
|
}>();
|
|
12
13
|
|
|
13
14
|
const nav = useNavigation();
|
|
15
|
+
const can = $derived(accessControl?.enabled ? useCan(resource, 'show') : null);
|
|
16
|
+
const hidden = $derived(accessControl?.hideIfUnauthorized && can && !can.allowed);
|
|
14
17
|
</script>
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
{#if !hidden}
|
|
20
|
+
|
|
21
|
+
<Button
|
|
22
|
+
variant="ghost"
|
|
23
|
+
size={hideText ? 'icon' : 'sm'}
|
|
24
|
+
class={className}
|
|
25
|
+
disabled={can ? !can.allowed : false}
|
|
26
|
+
onclick={() => nav.show(resource, recordItemId)}
|
|
27
|
+
>
|
|
28
|
+
<Eye class="h-4 w-4" />
|
|
29
|
+
{#if !hideText}<span class="ml-1">{t('common.detail')}</span>{/if}
|
|
30
|
+
</Button>
|
|
31
|
+
{/if}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// AvatarSize type is exported from the package index.ts
|
|
20
20
|
type AvatarSize = VariantProps<typeof avatarVariants>["size"];
|
|
21
21
|
|
|
22
|
-
type Props = WithElementRef<HTMLImgAttributes> & {
|
|
22
|
+
type Props = WithElementRef<HTMLImgAttributes, HTMLImageElement> & {
|
|
23
23
|
size?: AvatarSize;
|
|
24
24
|
fallback?: string;
|
|
25
25
|
};
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
|
35
35
|
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
|
|
36
36
|
|
|
37
|
-
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
|
38
|
-
WithElementRef<HTMLAnchorAttributes> & {
|
|
37
|
+
export type ButtonProps = WithElementRef<HTMLButtonAttributes, HTMLButtonElement> &
|
|
38
|
+
WithElementRef<HTMLAnchorAttributes, HTMLAnchorElement> & {
|
|
39
39
|
variant?: ButtonVariant;
|
|
40
40
|
size?: ButtonSize;
|
|
41
41
|
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<div
|
|
14
14
|
bind:this={ref}
|
|
15
15
|
data-slot="card-footer"
|
|
16
|
-
class={cn("bg-muted/50 rounded-b-xl
|
|
16
|
+
class={cn("bg-muted/50 rounded-b-xl p-4 group-data-[size=sm]/card:p-3 flex items-center", className)}
|
|
17
17
|
{...restProps}
|
|
18
18
|
>
|
|
19
19
|
{@render children?.()}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
bind:this={ref}
|
|
16
16
|
data-slot="card"
|
|
17
17
|
data-size={size}
|
|
18
|
-
class={cn("
|
|
18
|
+
class={cn("shadow-sm bg-card text-card-foreground gap-4 overflow-hidden rounded-lg py-4 text-sm has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col", className)}
|
|
19
19
|
{...restProps}
|
|
20
20
|
>
|
|
21
21
|
{@render children?.()}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLButtonAttributes> & {
|
|
5
|
+
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement> & {
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
onCheckedChange?: (checked: boolean) => void;
|
|
8
8
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLButtonAttributes> & {
|
|
5
|
+
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement> & {
|
|
6
6
|
inset?: boolean;
|
|
7
7
|
destructive?: boolean;
|
|
8
8
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLButtonAttributes>;
|
|
5
|
+
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement>;
|
|
6
6
|
|
|
7
7
|
let {
|
|
8
8
|
ref = $bindable(null),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from '../../../utils.js';
|
|
3
3
|
import type { HTMLLabelAttributes } from 'svelte/elements';
|
|
4
4
|
|
|
5
|
-
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLLabelAttributes> = $props();
|
|
5
|
+
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLLabelAttributes, HTMLLabelElement> = $props();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<label
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLSelectAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLSelectAttributes> & {
|
|
5
|
+
type Props = WithElementRef<HTMLSelectAttributes, HTMLSelectElement> & {
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
};
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLButtonAttributes> & {
|
|
5
|
+
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement> & {
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
onCheckedChange?: (checked: boolean) => void;
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<tbody bind:this={ref} data-slot="table-body" class={cn(
|
|
13
|
+
<tbody bind:this={ref} data-slot="table-body" class={cn(className)} {...restProps}>
|
|
14
14
|
{@render children?.()}
|
|
15
15
|
</tbody>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
class: className,
|
|
8
8
|
children,
|
|
9
9
|
...restProps
|
|
10
|
-
}: WithElementRef<HTMLTdAttributes> = $props();
|
|
10
|
+
}: WithElementRef<HTMLTdAttributes, HTMLTableCellElement> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<td bind:this={ref} data-slot="table-cell" class={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
class: className,
|
|
8
8
|
children,
|
|
9
9
|
...restProps
|
|
10
|
-
}: WithElementRef<HTMLThAttributes> = $props();
|
|
10
|
+
}: WithElementRef<HTMLThAttributes, HTMLTableCellElement> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<th bind:this={ref} data-slot="table-head" class={cn("text-foreground h-9 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
}: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<tr bind:this={ref} data-slot="table-row" class={cn("hover:bg-muted/50 data-[state=selected]:bg-muted
|
|
13
|
+
<tr bind:this={ref} data-slot="table-row" class={cn("hover:bg-muted/50 data-[state=selected]:bg-muted transition-colors", className)} {...restProps}>
|
|
14
14
|
{@render children?.()}
|
|
15
15
|
</tr>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
class: className,
|
|
8
8
|
children,
|
|
9
9
|
...restProps
|
|
10
|
-
}: WithElementRef<HTMLTableAttributes> = $props();
|
|
10
|
+
}: WithElementRef<HTMLTableAttributes, HTMLTableElement> = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<div data-slot="table-container" class="relative w-full overflow-x-auto">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
3
3
|
import type { HTMLButtonAttributes } from "svelte/elements";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLButtonAttributes> & {
|
|
5
|
+
type Props = WithElementRef<HTMLButtonAttributes, HTMLButtonElement> & {
|
|
6
6
|
value: string;
|
|
7
7
|
active?: boolean;
|
|
8
8
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { HTMLTextareaAttributes } from "svelte/elements";
|
|
3
3
|
import { cn, type WithElementRef } from "../../../utils.js";
|
|
4
4
|
|
|
5
|
-
type Props = WithElementRef<HTMLTextareaAttributes>;
|
|
5
|
+
type Props = WithElementRef<HTMLTextareaAttributes, HTMLTextAreaElement>;
|
|
6
6
|
|
|
7
7
|
let {
|
|
8
8
|
ref = $bindable(null),
|