@svadmin/ui 0.5.13 → 0.6.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/app.css +50 -63
- package/src/components/AdminApp.svelte +1 -1
- package/src/components/AutoForm.svelte +2 -2
- package/src/components/AutoTable.svelte +2 -2
- package/src/components/ConfigErrorScreen.svelte +5 -5
- package/src/components/EmptyState.svelte +1 -1
- package/src/components/ForgotPasswordPage.svelte +7 -7
- package/src/components/Header.svelte +9 -10
- package/src/components/Layout.svelte +2 -2
- package/src/components/LoginPage.svelte +8 -7
- package/src/components/PageHeader.svelte +1 -1
- package/src/components/ProfilePage.svelte +2 -2
- package/src/components/RegisterPage.svelte +6 -6
- package/src/components/Sidebar.svelte +159 -128
- package/src/components/StatsCard.svelte +4 -4
- package/src/components/UndoableNotification.svelte +1 -1
- package/src/components/UpdatePasswordPage.svelte +6 -6
- 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/dialog/dialog-content.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.6.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",
|
package/src/app.css
CHANGED
|
@@ -1,110 +1,100 @@
|
|
|
1
|
-
/* Typography — Inter font
|
|
1
|
+
/* Typography — Inter font for clean, modern look */
|
|
2
2
|
|
|
3
|
-
/* @svadmin/ui — Design Tokens (
|
|
3
|
+
/* @svadmin/ui — Design Tokens (Elygate-inspired)
|
|
4
4
|
* Auto-imported by AdminApp. Can be overridden by consumer's CSS.
|
|
5
5
|
*
|
|
6
|
-
* Based on
|
|
7
|
-
*
|
|
8
|
-
* - Smaller border-radius (0.5rem vs 0.625rem)
|
|
9
|
-
* - Refined color palette with deeper contrast
|
|
10
|
-
* - Chart color tokens included
|
|
11
|
-
* - More polished dark mode
|
|
6
|
+
* Based on Elygate dashboard style: Indigo primary, light glassmorphism,
|
|
7
|
+
* professional admin aesthetic with subtle depth.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
/* Auto-register svadmin source files for Tailwind 4 content scanning.
|
|
15
|
-
* This ensures all utility classes used in @svadmin/ui and @svadmin/core
|
|
16
|
-
* components are generated without manual @source in consumer projects. */
|
|
17
|
-
@source "@svadmin/ui/src/**/*.{svelte,ts,js}";
|
|
18
|
-
@source "@svadmin/core/src/**/*.{svelte,ts,js}";
|
|
19
|
-
|
|
20
10
|
@layer base {
|
|
21
11
|
:root {
|
|
22
12
|
--radius: 0.5rem;
|
|
23
|
-
--background: oklch(
|
|
24
|
-
--foreground: oklch(0.
|
|
13
|
+
--background: oklch(0.98 0.002 264);
|
|
14
|
+
--foreground: oklch(0.13 0.02 265);
|
|
25
15
|
--card: oklch(1 0 0);
|
|
26
|
-
--card-foreground: oklch(0.
|
|
16
|
+
--card-foreground: oklch(0.13 0.02 265);
|
|
27
17
|
--popover: oklch(1 0 0);
|
|
28
|
-
--popover-foreground: oklch(0.
|
|
29
|
-
--primary: oklch(0.
|
|
18
|
+
--popover-foreground: oklch(0.13 0.02 265);
|
|
19
|
+
--primary: oklch(0.488 0.243 264.376);
|
|
30
20
|
--primary-foreground: oklch(0.985 0 0);
|
|
31
|
-
--secondary: oklch(0.
|
|
32
|
-
--secondary-foreground: oklch(0.
|
|
33
|
-
--muted: oklch(0.
|
|
34
|
-
--muted-foreground: oklch(0.45 0
|
|
35
|
-
--accent: oklch(0.
|
|
36
|
-
--accent-foreground: oklch(0.
|
|
21
|
+
--secondary: oklch(0.955 0.008 264);
|
|
22
|
+
--secondary-foreground: oklch(0.2 0.04 265);
|
|
23
|
+
--muted: oklch(0.955 0.005 264);
|
|
24
|
+
--muted-foreground: oklch(0.45 0.03 260);
|
|
25
|
+
--accent: oklch(0.955 0.008 264);
|
|
26
|
+
--accent-foreground: oklch(0.2 0.04 265);
|
|
37
27
|
--destructive: oklch(0.577 0.245 27.325);
|
|
38
28
|
--destructive-foreground: oklch(0.985 0 0);
|
|
39
|
-
--border: oklch(0.
|
|
40
|
-
--input: oklch(0.
|
|
41
|
-
--ring: oklch(0.
|
|
42
|
-
--chart-1: oklch(0.
|
|
29
|
+
--border: oklch(0.912 0.008 264);
|
|
30
|
+
--input: oklch(0.912 0.008 264);
|
|
31
|
+
--ring: oklch(0.488 0.243 264.376);
|
|
32
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
43
33
|
--chart-2: oklch(0.6 0.118 184.704);
|
|
44
34
|
--chart-3: oklch(0.398 0.07 227.392);
|
|
45
35
|
--chart-4: oklch(0.828 0.189 84.429);
|
|
46
36
|
--chart-5: oklch(0.769 0.188 70.08);
|
|
47
|
-
--sidebar: oklch(0
|
|
48
|
-
--sidebar-foreground: oklch(0.
|
|
49
|
-
--sidebar-primary: oklch(0.
|
|
37
|
+
--sidebar: oklch(1 0 0 / 80%);
|
|
38
|
+
--sidebar-foreground: oklch(0.13 0.02 265);
|
|
39
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
50
40
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
51
|
-
--sidebar-accent: oklch(0.
|
|
52
|
-
--sidebar-accent-foreground: oklch(0.
|
|
53
|
-
--sidebar-border: oklch(0.
|
|
54
|
-
--sidebar-ring: oklch(0.
|
|
41
|
+
--sidebar-accent: oklch(0.94 0.025 264);
|
|
42
|
+
--sidebar-accent-foreground: oklch(0.33 0.17 264);
|
|
43
|
+
--sidebar-border: oklch(0.912 0.008 264);
|
|
44
|
+
--sidebar-ring: oklch(0.488 0.243 264.376);
|
|
55
45
|
}
|
|
56
46
|
|
|
57
47
|
.dark {
|
|
58
|
-
--background: oklch(0.
|
|
48
|
+
--background: oklch(0.13 0.015 265);
|
|
59
49
|
--foreground: oklch(0.985 0 0);
|
|
60
|
-
--card: oklch(0.
|
|
50
|
+
--card: oklch(0.16 0.015 265);
|
|
61
51
|
--card-foreground: oklch(0.985 0 0);
|
|
62
|
-
--popover: oklch(0.
|
|
52
|
+
--popover: oklch(0.16 0.015 265);
|
|
63
53
|
--popover-foreground: oklch(0.985 0 0);
|
|
64
|
-
--primary: oklch(0.
|
|
65
|
-
--primary-foreground: oklch(0.
|
|
66
|
-
--secondary: oklch(0.
|
|
54
|
+
--primary: oklch(0.585 0.233 264);
|
|
55
|
+
--primary-foreground: oklch(0.15 0.04 265);
|
|
56
|
+
--secondary: oklch(0.23 0.025 265);
|
|
67
57
|
--secondary-foreground: oklch(0.985 0 0);
|
|
68
|
-
--muted: oklch(0.
|
|
69
|
-
--muted-foreground: oklch(0.
|
|
70
|
-
--accent: oklch(0.
|
|
58
|
+
--muted: oklch(0.23 0.025 265);
|
|
59
|
+
--muted-foreground: oklch(0.7 0.02 260);
|
|
60
|
+
--accent: oklch(0.23 0.025 265);
|
|
71
61
|
--accent-foreground: oklch(0.985 0 0);
|
|
72
62
|
--destructive: oklch(0.704 0.191 22.216);
|
|
73
63
|
--destructive-foreground: oklch(0.985 0 0);
|
|
74
|
-
--border: oklch(
|
|
75
|
-
--input: oklch(
|
|
76
|
-
--ring: oklch(0.
|
|
77
|
-
--chart-1: oklch(0.
|
|
64
|
+
--border: oklch(0.23 0.025 265);
|
|
65
|
+
--input: oklch(0.23 0.025 265);
|
|
66
|
+
--ring: oklch(0.55 0.18 264);
|
|
67
|
+
--chart-1: oklch(0.585 0.233 264);
|
|
78
68
|
--chart-2: oklch(0.696 0.17 162.48);
|
|
79
69
|
--chart-3: oklch(0.769 0.188 70.08);
|
|
80
70
|
--chart-4: oklch(0.627 0.265 303.9);
|
|
81
71
|
--chart-5: oklch(0.645 0.246 16.439);
|
|
82
|
-
--sidebar: oklch(0.
|
|
72
|
+
--sidebar: oklch(0.16 0.015 265 / 80%);
|
|
83
73
|
--sidebar-foreground: oklch(0.985 0 0);
|
|
84
|
-
--sidebar-primary: oklch(0.
|
|
74
|
+
--sidebar-primary: oklch(0.585 0.233 264);
|
|
85
75
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
86
|
-
--sidebar-accent: oklch(0.
|
|
87
|
-
--sidebar-accent-foreground: oklch(0.
|
|
88
|
-
--sidebar-border: oklch(
|
|
89
|
-
--sidebar-ring: oklch(0.
|
|
76
|
+
--sidebar-accent: oklch(0.488 0.243 264.376 / 10%);
|
|
77
|
+
--sidebar-accent-foreground: oklch(0.7 0.15 264);
|
|
78
|
+
--sidebar-border: oklch(0.23 0.025 265);
|
|
79
|
+
--sidebar-ring: oklch(0.55 0.18 264);
|
|
90
80
|
}
|
|
91
81
|
|
|
92
|
-
/*
|
|
82
|
+
/* Elygate-style shadows — subtle depth without heaviness */
|
|
93
83
|
[data-slot="button"],
|
|
94
84
|
[data-slot="input"],
|
|
95
85
|
[data-slot="select-trigger"],
|
|
96
86
|
[data-slot="select"],
|
|
97
87
|
[data-slot="textarea"] {
|
|
98
|
-
|
|
88
|
+
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
99
89
|
}
|
|
100
90
|
|
|
101
91
|
[data-slot="card"] {
|
|
102
|
-
|
|
92
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.06), 0 1px 2px -1px rgb(0 0 0 / 0.06);
|
|
103
93
|
}
|
|
104
94
|
|
|
105
95
|
[data-slot="dialog-content"],
|
|
106
96
|
[data-slot="sheet-content"] {
|
|
107
|
-
|
|
97
|
+
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
108
98
|
}
|
|
109
99
|
}
|
|
110
100
|
|
|
@@ -122,10 +112,7 @@ body {
|
|
|
122
112
|
.dark ::-webkit-scrollbar-thumb { background: oklch(0.35 0 0); }
|
|
123
113
|
.dark ::-webkit-scrollbar-thumb:hover { background: oklch(0.45 0 0); }
|
|
124
114
|
|
|
125
|
-
/* Tailwind CSS v4 — Map design tokens to utility classes
|
|
126
|
-
* This enables bg-primary, text-muted-foreground, border-input, etc.
|
|
127
|
-
* Without this, Tailwind 4 utility classes referencing these tokens are no-ops.
|
|
128
|
-
*/
|
|
115
|
+
/* Tailwind CSS v4 — Map design tokens to utility classes */
|
|
129
116
|
@theme {
|
|
130
117
|
--color-background: var(--background);
|
|
131
118
|
--color-foreground: var(--foreground);
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
{@render dashboard()}
|
|
120
120
|
{:else}
|
|
121
121
|
<div class="space-y-4">
|
|
122
|
-
<h1 class="text-
|
|
122
|
+
<h1 class="text-xl font-semibold text-foreground">{t('common.welcome', { title })}</h1>
|
|
123
123
|
<p class="text-muted-foreground">{t('common.dashboardHint')}</p>
|
|
124
124
|
</div>
|
|
125
125
|
{/if}
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
>
|
|
223
223
|
<ArrowLeft class="h-5 w-5" />
|
|
224
224
|
</TooltipButton>
|
|
225
|
-
<h1 class="text-
|
|
225
|
+
<h1 class="text-xl font-semibold text-foreground">{pageTitle}</h1>
|
|
226
226
|
{#if isDirty}
|
|
227
227
|
<Badge variant="outline" class="border-amber-200 bg-amber-50 text-amber-700">{t('common.unsaved')}</Badge>
|
|
228
228
|
{/if}
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
|
|
231
231
|
{#if isLoading}
|
|
232
232
|
<div class="max-w-3xl space-y-6">
|
|
233
|
-
<div class="rounded-
|
|
233
|
+
<div class="rounded-lg border p-6 space-y-5">
|
|
234
234
|
{#each Array(4) as _}
|
|
235
235
|
<div class="space-y-2">
|
|
236
236
|
<Skeleton class="h-4 w-24" />
|
|
@@ -311,7 +311,7 @@
|
|
|
311
311
|
<div class="space-y-4">
|
|
312
312
|
<!-- Header -->
|
|
313
313
|
<div class="flex items-center justify-between">
|
|
314
|
-
<h1 class="text-
|
|
314
|
+
<h1 class="text-xl font-semibold text-foreground">{resource.label}</h1>
|
|
315
315
|
<div class="flex items-center gap-2">
|
|
316
316
|
{#if canExport}
|
|
317
317
|
<Button variant="outline" size="sm" onclick={exportCSV}>
|
|
@@ -414,7 +414,7 @@
|
|
|
414
414
|
{/if}
|
|
415
415
|
|
|
416
416
|
<!-- Table (TanStack-powered) -->
|
|
417
|
-
<div class="rounded-
|
|
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">
|
|
@@ -48,14 +48,14 @@
|
|
|
48
48
|
}
|
|
49
49
|
</script>
|
|
50
50
|
|
|
51
|
-
<div class="min-h-screen flex items-center justify-center bg-
|
|
51
|
+
<div class="min-h-screen flex items-center justify-center bg-background p-4">
|
|
52
52
|
<div class="w-full max-w-[480px]">
|
|
53
|
-
<Card.Card
|
|
53
|
+
<Card.Card>
|
|
54
54
|
<Card.CardHeader class="text-center pb-2">
|
|
55
|
-
<div class="inline-flex items-center justify-center w-
|
|
56
|
-
<AlertTriangle class="h-
|
|
55
|
+
<div class="inline-flex items-center justify-center w-12 h-12 rounded-lg bg-destructive/10 text-destructive mx-auto mb-3">
|
|
56
|
+
<AlertTriangle class="h-6 w-6" />
|
|
57
57
|
</div>
|
|
58
|
-
<Card.CardTitle class="text-xl
|
|
58
|
+
<Card.CardTitle class="text-xl">{displayTitle}</Card.CardTitle>
|
|
59
59
|
<p class="text-sm text-muted-foreground">
|
|
60
60
|
{t('config.missingEnvDescription')}
|
|
61
61
|
</p>
|
|
@@ -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">
|
|
@@ -34,18 +34,18 @@
|
|
|
34
34
|
}
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
|
-
<div class="min-h-screen flex items-center justify-center bg-
|
|
38
|
-
<div class="w-full max-w-[
|
|
39
|
-
<Card.Card
|
|
37
|
+
<div class="min-h-screen flex items-center justify-center bg-background p-4">
|
|
38
|
+
<div class="w-full max-w-[380px]">
|
|
39
|
+
<Card.Card>
|
|
40
40
|
<Card.CardHeader class="text-center pb-2">
|
|
41
|
-
<div class="inline-flex items-center justify-center w-
|
|
41
|
+
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
42
42
|
{#if sent}
|
|
43
|
-
<CheckCircle class="h-
|
|
43
|
+
<CheckCircle class="h-5 w-5" />
|
|
44
44
|
{:else}
|
|
45
|
-
<KeyRound class="h-
|
|
45
|
+
<KeyRound class="h-5 w-5" />
|
|
46
46
|
{/if}
|
|
47
47
|
</div>
|
|
48
|
-
<Card.CardTitle class="text-
|
|
48
|
+
<Card.CardTitle class="text-xl">
|
|
49
49
|
{sent ? t('auth.resetLinkSent') : t('auth.forgotPassword')}
|
|
50
50
|
</Card.CardTitle>
|
|
51
51
|
{#if !sent}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { t } from '@svadmin/core/i18n';
|
|
8
8
|
|
|
9
9
|
let {
|
|
10
|
-
showThemeToggle =
|
|
10
|
+
showThemeToggle = false,
|
|
11
11
|
showBreadcrumbs = true,
|
|
12
12
|
showSearch = true,
|
|
13
13
|
onSearchClick,
|
|
@@ -21,20 +21,19 @@
|
|
|
21
21
|
}>();
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
|
-
<header class="sticky top-0 z-30 flex h-
|
|
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
|
+
{#if children}
|
|
27
|
+
{@render children()}
|
|
28
|
+
{/if}
|
|
26
29
|
{#if showBreadcrumbs}
|
|
27
30
|
<Breadcrumbs />
|
|
28
31
|
{/if}
|
|
29
32
|
</div>
|
|
30
33
|
<div class="ml-auto flex items-center gap-2">
|
|
31
|
-
{#if children}
|
|
32
|
-
{@render children()}
|
|
33
|
-
{/if}
|
|
34
|
-
|
|
35
34
|
{#if showSearch && onSearchClick}
|
|
36
|
-
<Button variant="outline" size="sm" onclick={onSearchClick} class="gap-2 text-muted-foreground">
|
|
37
|
-
<Search class="h-
|
|
35
|
+
<Button variant="outline" size="sm" onclick={onSearchClick} class="gap-2 text-muted-foreground h-8 px-3">
|
|
36
|
+
<Search class="h-3.5 w-3.5" />
|
|
38
37
|
<span class="hidden sm:inline text-xs">Search...</span>
|
|
39
38
|
<kbd class="hidden sm:inline-flex items-center gap-0.5 rounded border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
40
39
|
<span class="text-xs">⌘</span>K
|
|
@@ -45,9 +44,9 @@
|
|
|
45
44
|
{#if showThemeToggle}
|
|
46
45
|
<TooltipButton tooltip={t('common.toggleTheme')} onclick={() => toggleTheme()} class="rounded-full">
|
|
47
46
|
{#if getResolvedTheme() === 'dark'}
|
|
48
|
-
<Moon class="h-
|
|
47
|
+
<Moon class="h-4 w-4 transition-all" />
|
|
49
48
|
{:else}
|
|
50
|
-
<Sun class="h-
|
|
49
|
+
<Sun class="h-4 w-4 transition-all" />
|
|
51
50
|
{/if}
|
|
52
51
|
</TooltipButton>
|
|
53
52
|
{/if}
|
|
@@ -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 _}
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
</div>
|
|
83
83
|
</div>
|
|
84
84
|
{:else}
|
|
85
|
-
<div class="flex h-screen bg-
|
|
85
|
+
<div class="flex h-screen bg-slate-50 dark:bg-slate-950" in:fade={{ duration: 200, delay: 50 }}>
|
|
86
86
|
<!-- Desktop sidebar -->
|
|
87
87
|
<div class="hidden md:block">
|
|
88
88
|
<Sidebar {collapsed} {identity} {title} onToggle={() => collapsed = !collapsed} onLogout={handleLogout} />
|
|
@@ -61,14 +61,14 @@
|
|
|
61
61
|
}
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
|
-
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-
|
|
65
|
-
<div class="w-full max-w-[
|
|
66
|
-
<Card.Card
|
|
64
|
+
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-100 via-blue-50/30 to-indigo-50/40 dark:from-slate-950 dark:via-slate-900 dark:to-indigo-950/20 p-4">
|
|
65
|
+
<div class="w-full max-w-[380px]">
|
|
66
|
+
<Card.Card>
|
|
67
67
|
<Card.CardHeader class="text-center pb-2">
|
|
68
|
-
<div class="inline-flex items-center justify-center w-
|
|
69
|
-
<LogIn class="h-
|
|
68
|
+
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
69
|
+
<LogIn class="h-5 w-5" />
|
|
70
70
|
</div>
|
|
71
|
-
<Card.CardTitle class="text-
|
|
71
|
+
<Card.CardTitle class="text-xl">{t('auth.welcomeBack')}</Card.CardTitle>
|
|
72
72
|
<p class="text-sm text-muted-foreground">{t('auth.welcomeMessage')}</p>
|
|
73
73
|
</Card.CardHeader>
|
|
74
74
|
<Card.CardContent>
|
|
@@ -155,8 +155,9 @@
|
|
|
155
155
|
</Card.CardContent>
|
|
156
156
|
</Card.Card>
|
|
157
157
|
|
|
158
|
-
<p class="text-xs text-muted-foreground mt-4 text-center
|
|
158
|
+
<p class="text-xs text-muted-foreground mt-4 text-center">
|
|
159
159
|
Powered by {title}
|
|
160
160
|
</p>
|
|
161
161
|
</div>
|
|
162
162
|
</div>
|
|
163
|
+
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
{/if}
|
|
26
26
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
27
27
|
<div>
|
|
28
|
-
<h1 class="text-
|
|
28
|
+
<h1 class="text-xl font-semibold tracking-tight text-foreground">{title}</h1>
|
|
29
29
|
{#if description}
|
|
30
30
|
<p class="text-sm text-muted-foreground">{description}</p>
|
|
31
31
|
{/if}
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
<img
|
|
73
73
|
src={identity.data.avatar}
|
|
74
74
|
alt={identity.data.name ?? ''}
|
|
75
|
-
class="h-20 w-20 rounded-
|
|
75
|
+
class="h-20 w-20 rounded-lg object-cover ring-2 ring-primary/20"
|
|
76
76
|
/>
|
|
77
77
|
{:else}
|
|
78
|
-
<div class="h-20 w-20 rounded-
|
|
78
|
+
<div class="h-20 w-20 rounded-lg bg-primary/10 text-primary flex items-center justify-center text-2xl font-bold">
|
|
79
79
|
{initials}
|
|
80
80
|
</div>
|
|
81
81
|
{/if}
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
}
|
|
40
40
|
</script>
|
|
41
41
|
|
|
42
|
-
<div class="min-h-screen flex items-center justify-center bg-
|
|
43
|
-
<div class="w-full max-w-[
|
|
44
|
-
<Card.Card
|
|
42
|
+
<div class="min-h-screen flex items-center justify-center bg-background p-4">
|
|
43
|
+
<div class="w-full max-w-[380px]">
|
|
44
|
+
<Card.Card>
|
|
45
45
|
<Card.CardHeader class="text-center pb-2">
|
|
46
|
-
<div class="inline-flex items-center justify-center w-
|
|
47
|
-
<UserPlus class="h-
|
|
46
|
+
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
47
|
+
<UserPlus class="h-5 w-5" />
|
|
48
48
|
</div>
|
|
49
|
-
<Card.CardTitle class="text-
|
|
49
|
+
<Card.CardTitle class="text-xl">{t('auth.createAccount')}</Card.CardTitle>
|
|
50
50
|
<p class="text-sm text-muted-foreground">{t('auth.createAccountMessage')}</p>
|
|
51
51
|
</Card.CardHeader>
|
|
52
52
|
<Card.CardContent>
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getResources } from '@svadmin/core';
|
|
3
3
|
import type { Identity } from '@svadmin/core';
|
|
4
|
-
import { currentPath
|
|
4
|
+
import { currentPath } from '@svadmin/core/router';
|
|
5
5
|
import { t, getLocale, setLocale, getAvailableLocales } from '@svadmin/core/i18n';
|
|
6
6
|
import { toggleTheme, getResolvedTheme, colorThemes, getColorTheme, setColorTheme, canAccessAsync } from '@svadmin/core';
|
|
7
7
|
import { Button } from './ui/button/index.js';
|
|
8
8
|
import TooltipButton from './TooltipButton.svelte';
|
|
9
9
|
import * as Tooltip from './ui/tooltip/index.js';
|
|
10
|
-
|
|
11
|
-
import { Separator } from './ui/separator/index.js';
|
|
10
|
+
|
|
12
11
|
import { ScrollArea } from './ui/scroll-area/index.js';
|
|
13
12
|
import * as Collapsible from './ui/collapsible/index.js';
|
|
14
13
|
import { Avatar } from './ui/avatar/index.js';
|
|
15
14
|
import {
|
|
16
15
|
LayoutDashboard, FileText, Users, Settings, Home,
|
|
17
|
-
ChevronLeft,
|
|
16
|
+
ChevronLeft, ChevronDown, LogOut, Sun, Moon, Palette
|
|
18
17
|
} from 'lucide-svelte';
|
|
19
18
|
|
|
20
19
|
let { collapsed, identity, title, onToggle, onLogout } = $props<{
|
|
@@ -118,34 +117,57 @@
|
|
|
118
117
|
|
|
119
118
|
// Track which groups are open
|
|
120
119
|
let openGroups = $state<Set<string>>(new Set());
|
|
120
|
+
let colorPickerOpen = $state(false);
|
|
121
|
+
let colorPickerRef = $state<HTMLDivElement | null>(null);
|
|
122
|
+
let colorPickerOpenedAt = 0;
|
|
123
|
+
|
|
124
|
+
// Click-outside to close color picker
|
|
125
|
+
$effect(() => {
|
|
126
|
+
if (!colorPickerOpen) return;
|
|
127
|
+
function handleMouseDown(e: MouseEvent) {
|
|
128
|
+
// Ignore if opened within last 200ms (same interaction)
|
|
129
|
+
if (Date.now() - colorPickerOpenedAt < 200) return;
|
|
130
|
+
if (colorPickerRef && !colorPickerRef.contains(e.target as Node)) {
|
|
131
|
+
colorPickerOpen = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
document.addEventListener('mousedown', handleMouseDown, true);
|
|
135
|
+
return () => {
|
|
136
|
+
document.removeEventListener('mousedown', handleMouseDown, true);
|
|
137
|
+
};
|
|
138
|
+
});
|
|
121
139
|
</script>
|
|
122
140
|
|
|
123
141
|
<svelte:window onhashchange={onHashChange} />
|
|
124
142
|
|
|
125
143
|
<aside
|
|
126
144
|
aria-label="Sidebar navigation"
|
|
127
|
-
class="fixed inset-y-0 left-0 z-30 flex flex-col
|
|
145
|
+
class="fixed inset-y-0 left-0 z-30 flex flex-col backdrop-blur-xl transition-all duration-300"
|
|
146
|
+
style="background-color: var(--sidebar);"
|
|
128
147
|
class:w-64={!collapsed}
|
|
129
148
|
class:w-16={collapsed}
|
|
130
149
|
>
|
|
131
150
|
<!-- Logo -->
|
|
132
|
-
<div class="flex h-16 items-center justify-between
|
|
151
|
+
<div class="flex h-16 items-center justify-between px-4">
|
|
133
152
|
{#if !collapsed}
|
|
134
|
-
<
|
|
153
|
+
<div class="flex items-center gap-3">
|
|
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>
|
|
155
|
+
<span class="font-semibold text-lg tracking-tight text-sidebar-foreground">{title}</span>
|
|
156
|
+
</div>
|
|
135
157
|
{:else}
|
|
136
|
-
<
|
|
158
|
+
<div class="w-8 h-8 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 mx-auto" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
|
|
137
159
|
{/if}
|
|
138
|
-
|
|
139
|
-
{
|
|
140
|
-
<ChevronRight class="h-4 w-4" />
|
|
141
|
-
{:else}
|
|
160
|
+
{#if !collapsed}
|
|
161
|
+
<TooltipButton tooltip={t('common.toggleSidebar')} variant="ghost" size="icon-sm" onclick={onToggle}>
|
|
142
162
|
<ChevronLeft class="h-4 w-4" />
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
</TooltipButton>
|
|
164
|
+
{:else}
|
|
165
|
+
<div></div>
|
|
166
|
+
{/if}
|
|
145
167
|
</div>
|
|
146
168
|
|
|
147
169
|
<ScrollArea class="flex-1">
|
|
148
|
-
<nav aria-label="Main menu" class="py-4 px-
|
|
170
|
+
<nav aria-label="Main menu" class="py-4 px-3 space-y-4">
|
|
149
171
|
{#each navGroups as group}
|
|
150
172
|
{#if group.name && !collapsed}
|
|
151
173
|
<!-- Grouped section with Collapsible -->
|
|
@@ -155,24 +177,24 @@
|
|
|
155
177
|
openGroups = next;
|
|
156
178
|
}}>
|
|
157
179
|
<Collapsible.Trigger
|
|
158
|
-
class="flex w-full items-center justify-between
|
|
180
|
+
class="flex w-full items-center justify-between px-3 py-1.5 text-[10px] font-bold uppercase tracking-wider text-sidebar-foreground/40 hover:text-sidebar-foreground/60 transition-colors"
|
|
159
181
|
>
|
|
160
182
|
<span>{group.name}</span>
|
|
161
183
|
<ChevronDown class="h-3 w-3 transition-transform {openGroups.has(group.name) ? 'rotate-180' : ''}" />
|
|
162
184
|
</Collapsible.Trigger>
|
|
163
185
|
<Collapsible.Content>
|
|
164
|
-
<div class="mt-1 space-y-
|
|
186
|
+
<div class="mt-1 space-y-1">
|
|
165
187
|
{#each group.items as item}
|
|
166
188
|
{@const active = isActive(item.path)}
|
|
167
189
|
<a
|
|
168
190
|
href={`#${item.path}`}
|
|
169
|
-
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-all duration-200
|
|
192
|
+
{active
|
|
193
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
194
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
195
|
+
style={active ? 'box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);' : ''}
|
|
174
196
|
>
|
|
175
|
-
<item.Icon class="h-
|
|
197
|
+
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
176
198
|
<span>{item.label}</span>
|
|
177
199
|
</a>
|
|
178
200
|
{/each}
|
|
@@ -190,13 +212,12 @@
|
|
|
190
212
|
<a
|
|
191
213
|
{...props}
|
|
192
214
|
href={`#${item.path}`}
|
|
193
|
-
class="flex items-center justify-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class:hover:bg-sidebar-accent={!active}
|
|
215
|
+
class="flex items-center justify-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
216
|
+
{active
|
|
217
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
218
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
198
219
|
>
|
|
199
|
-
<item.Icon class="h-
|
|
220
|
+
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
200
221
|
</a>
|
|
201
222
|
{/snippet}
|
|
202
223
|
</Tooltip.Trigger>
|
|
@@ -207,13 +228,13 @@
|
|
|
207
228
|
{:else}
|
|
208
229
|
<a
|
|
209
230
|
href={`#${item.path}`}
|
|
210
|
-
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200
|
|
232
|
+
{active
|
|
233
|
+
? 'bg-sidebar-accent text-sidebar-accent-foreground'
|
|
234
|
+
: 'text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground'}"
|
|
235
|
+
style={active ? 'box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);' : ''}
|
|
215
236
|
>
|
|
216
|
-
<item.Icon class="h-
|
|
237
|
+
<item.Icon class="h-4 w-4 flex-shrink-0" />
|
|
217
238
|
<span>{item.label}</span>
|
|
218
239
|
</a>
|
|
219
240
|
{/if}
|
|
@@ -224,111 +245,121 @@
|
|
|
224
245
|
</ScrollArea>
|
|
225
246
|
|
|
226
247
|
<!-- Footer -->
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
<!-- Color theme picker via DropdownMenu -->
|
|
248
|
+
<div>
|
|
249
|
+
<!-- Color theme picker -->
|
|
230
250
|
{#if !collapsed}
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
<div class="px-3 pt-3 pb-1">
|
|
252
|
+
<div class="relative" bind:this={colorPickerRef}>
|
|
253
|
+
<Button
|
|
254
|
+
variant="ghost"
|
|
255
|
+
class="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors h-auto justify-start"
|
|
256
|
+
onclick={() => { if (!colorPickerOpen) colorPickerOpenedAt = Date.now(); colorPickerOpen = !colorPickerOpen; }}
|
|
257
|
+
>
|
|
258
|
+
<Palette class="h-4 w-4" />
|
|
259
|
+
<span class="flex-1 text-left text-xs">{t('common.toggleTheme')}</span>
|
|
260
|
+
<span
|
|
261
|
+
class="h-3.5 w-3.5 rounded-full ring-1 ring-offset-1 ring-offset-sidebar"
|
|
262
|
+
style="background-color: {colorThemes.find(c => c.id === getColorTheme())?.color ?? '#6366f1'}; --tw-ring-color: {colorThemes.find(c => c.id === getColorTheme())?.color ?? '#6366f1'}"
|
|
263
|
+
></span>
|
|
264
|
+
</Button>
|
|
265
|
+
{#if colorPickerOpen}
|
|
266
|
+
<div class="absolute bottom-full left-0 mb-1 z-50 w-40 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95">
|
|
267
|
+
{#each colorThemes as ct}
|
|
268
|
+
<button
|
|
269
|
+
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"
|
|
270
|
+
onclick={() => { setColorTheme(ct.id); colorPickerOpen = false; }}
|
|
271
|
+
>
|
|
272
|
+
<span
|
|
273
|
+
class="h-3.5 w-3.5 rounded-full {getColorTheme() === ct.id ? 'ring-2 ring-offset-1 scale-110' : 'opacity-70'}"
|
|
274
|
+
style="background-color: {ct.color}; {getColorTheme() === ct.id ? `--tw-ring-color: ${ct.color}` : ''}"
|
|
275
|
+
></span>
|
|
276
|
+
<span class="text-xs">{ct.label}</span>
|
|
277
|
+
</button>
|
|
278
|
+
{/each}
|
|
279
|
+
</div>
|
|
280
|
+
{/if}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
260
283
|
{/if}
|
|
261
284
|
|
|
285
|
+
<!-- User info -->
|
|
262
286
|
{#if !collapsed && identity}
|
|
263
|
-
<div class="
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
<
|
|
287
|
+
<div class="px-3 pb-3 pt-1">
|
|
288
|
+
<div class="flex items-center gap-3 rounded-lg p-2 hover:bg-sidebar-accent/50 transition-colors">
|
|
289
|
+
<Avatar
|
|
290
|
+
src={(identity as Record<string, unknown>).avatar as string | undefined}
|
|
291
|
+
alt={identity.name ?? 'User'}
|
|
292
|
+
fallback={identity.name?.charAt(0).toUpperCase() ?? 'U'}
|
|
293
|
+
size="sm"
|
|
294
|
+
/>
|
|
295
|
+
<div class="flex-1 min-w-0">
|
|
296
|
+
<p class="truncate text-sm font-medium text-sidebar-foreground">{identity.name}</p>
|
|
297
|
+
<p class="truncate text-[10px] text-sidebar-foreground/50">Administrator</p>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="flex items-center justify-between px-1 mt-1">
|
|
301
|
+
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon-sm" onclick={toggleLocale} class="text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
302
|
+
<span class="text-xs font-bold">{localeLabel}</span>
|
|
303
|
+
</TooltipButton>
|
|
304
|
+
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class="text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
305
|
+
{#if getResolvedTheme() === 'dark'}
|
|
306
|
+
<Sun class="h-4 w-4" />
|
|
307
|
+
{:else}
|
|
308
|
+
<Moon class="h-4 w-4" />
|
|
309
|
+
{/if}
|
|
310
|
+
</TooltipButton>
|
|
311
|
+
<TooltipButton tooltip={t('common.logout')} variant="ghost" size="icon-sm" onclick={onLogout} class="text-sidebar-foreground/60 hover:text-destructive">
|
|
312
|
+
<LogOut class="h-4 w-4" />
|
|
313
|
+
</TooltipButton>
|
|
272
314
|
</div>
|
|
273
|
-
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon-sm" onclick={toggleLocale} class="text-sidebar-foreground">
|
|
274
|
-
<span class="text-xs font-bold">{localeLabel}</span>
|
|
275
|
-
</TooltipButton>
|
|
276
|
-
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon-sm" onclick={toggleTheme} class="text-sidebar-foreground">
|
|
277
|
-
{#if getResolvedTheme() === 'dark'}
|
|
278
|
-
<Sun class="h-4 w-4" />
|
|
279
|
-
{:else}
|
|
280
|
-
<Moon class="h-4 w-4" />
|
|
281
|
-
{/if}
|
|
282
|
-
</TooltipButton>
|
|
283
|
-
<TooltipButton tooltip={t('common.logout')} variant="ghost" size="icon-sm" onclick={onLogout} class="text-sidebar-foreground hover:text-destructive">
|
|
284
|
-
<LogOut class="h-4 w-4" />
|
|
285
|
-
</TooltipButton>
|
|
286
315
|
</div>
|
|
287
316
|
{:else if collapsed}
|
|
288
|
-
<
|
|
289
|
-
<Tooltip.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
<Tooltip.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<Tooltip.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
317
|
+
<div class="px-1 py-3 space-y-1">
|
|
318
|
+
<Tooltip.Root>
|
|
319
|
+
<Tooltip.Trigger>
|
|
320
|
+
{#snippet child({ props })}
|
|
321
|
+
<Button {...props} variant="ghost" size="icon" onclick={toggleLocale} class="w-full text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
322
|
+
<span class="text-xs font-bold">{localeLabel}</span>
|
|
323
|
+
</Button>
|
|
324
|
+
{/snippet}
|
|
325
|
+
</Tooltip.Trigger>
|
|
326
|
+
<Tooltip.Content side="right">{t('common.switchLanguage')}</Tooltip.Content>
|
|
327
|
+
</Tooltip.Root>
|
|
328
|
+
<Tooltip.Root>
|
|
329
|
+
<Tooltip.Trigger>
|
|
330
|
+
{#snippet child({ props })}
|
|
331
|
+
<Button {...props} variant="ghost" size="icon" onclick={toggleTheme} class="w-full text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
332
|
+
{#if getResolvedTheme() === 'dark'}
|
|
333
|
+
<Sun class="h-4 w-4" />
|
|
334
|
+
{:else}
|
|
335
|
+
<Moon class="h-4 w-4" />
|
|
336
|
+
{/if}
|
|
337
|
+
</Button>
|
|
338
|
+
{/snippet}
|
|
339
|
+
</Tooltip.Trigger>
|
|
340
|
+
<Tooltip.Content side="right">{t('common.toggleTheme')}</Tooltip.Content>
|
|
341
|
+
</Tooltip.Root>
|
|
342
|
+
<Tooltip.Root>
|
|
343
|
+
<Tooltip.Trigger>
|
|
344
|
+
{#snippet child({ props })}
|
|
345
|
+
<Button {...props} variant="ghost" size="icon" onclick={onLogout} class="w-full text-sidebar-foreground/60 hover:text-destructive">
|
|
346
|
+
<LogOut class="h-4 w-4" />
|
|
347
|
+
</Button>
|
|
348
|
+
{/snippet}
|
|
349
|
+
</Tooltip.Trigger>
|
|
350
|
+
<Tooltip.Content side="right">{t('common.logout')}</Tooltip.Content>
|
|
351
|
+
</Tooltip.Root>
|
|
352
|
+
</div>
|
|
322
353
|
{:else}
|
|
323
|
-
<div class="flex gap-1">
|
|
324
|
-
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon" onclick={toggleLocale} class="flex-1 text-sidebar-foreground">
|
|
354
|
+
<div class="flex gap-1 p-3">
|
|
355
|
+
<TooltipButton tooltip={t('common.switchLanguage')} variant="ghost" size="icon" onclick={toggleLocale} class="flex-1 text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
325
356
|
<span class="text-xs font-bold">{localeLabel}</span>
|
|
326
357
|
</TooltipButton>
|
|
327
|
-
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class="flex-1 text-sidebar-foreground">
|
|
358
|
+
<TooltipButton tooltip={t('common.toggleTheme')} variant="ghost" size="icon" onclick={toggleTheme} class="flex-1 text-sidebar-foreground/60 hover:text-sidebar-foreground">
|
|
328
359
|
{#if getResolvedTheme() === 'dark'}
|
|
329
|
-
<Sun class="h-
|
|
360
|
+
<Sun class="h-4 w-4" />
|
|
330
361
|
{:else}
|
|
331
|
-
<Moon class="h-
|
|
362
|
+
<Moon class="h-4 w-4" />
|
|
332
363
|
{/if}
|
|
333
364
|
</TooltipButton>
|
|
334
365
|
</div>
|
|
@@ -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
|
};
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
}: Props = $props();
|
|
46
46
|
</script>
|
|
47
47
|
|
|
48
|
-
<div class="flex items-center gap-4 rounded-
|
|
48
|
+
<div class="flex items-center gap-4 rounded-lg p-5 {variantMap[variant]} {className}">
|
|
49
49
|
{#if Icon}
|
|
50
|
-
<div class="flex h-12 w-12 items-center justify-center rounded-
|
|
50
|
+
<div class="flex h-12 w-12 items-center justify-center rounded-lg {colorMap[color]}">
|
|
51
51
|
<Icon class="h-6 w-6" />
|
|
52
52
|
</div>
|
|
53
53
|
{/if}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
<Skeleton class="mt-1 h-7 w-20" />
|
|
58
58
|
{:else}
|
|
59
59
|
<div class="flex items-baseline gap-2">
|
|
60
|
-
<p class="text-
|
|
60
|
+
<p class="text-xl font-semibold text-foreground">{value}</p>
|
|
61
61
|
{#if trend}
|
|
62
62
|
<span
|
|
63
63
|
class="text-xs font-medium {trend.value >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'}"
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
{#if !dismissed}
|
|
51
51
|
<div
|
|
52
|
-
class="fixed bottom-6 left-1/2 -translate-x-1/2 z-[100] min-w-80 max-w-lg rounded-
|
|
52
|
+
class="fixed bottom-6 left-1/2 -translate-x-1/2 z-[100] min-w-80 max-w-lg rounded-lg border bg-card shadow-lg overflow-hidden"
|
|
53
53
|
transition:slide={{ duration: 250 }}
|
|
54
54
|
>
|
|
55
55
|
<div class="flex items-center justify-between px-4 py-3 gap-3">
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
}
|
|
33
33
|
</script>
|
|
34
34
|
|
|
35
|
-
<div class="min-h-screen flex items-center justify-center bg-
|
|
36
|
-
<div class="w-full max-w-[
|
|
37
|
-
<Card.Card
|
|
35
|
+
<div class="min-h-screen flex items-center justify-center bg-background p-4">
|
|
36
|
+
<div class="w-full max-w-[380px]">
|
|
37
|
+
<Card.Card>
|
|
38
38
|
<Card.CardHeader class="text-center pb-2">
|
|
39
|
-
<div class="inline-flex items-center justify-center w-
|
|
40
|
-
<ShieldCheck class="h-
|
|
39
|
+
<div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
|
|
40
|
+
<ShieldCheck class="h-5 w-5" />
|
|
41
41
|
</div>
|
|
42
|
-
<Card.CardTitle class="text-
|
|
42
|
+
<Card.CardTitle class="text-xl">{t('auth.resetPassword')}</Card.CardTitle>
|
|
43
43
|
<p class="text-sm text-muted-foreground">{t('auth.resetPasswordDescription')}</p>
|
|
44
44
|
</Card.CardHeader>
|
|
45
45
|
<Card.CardContent>
|
|
@@ -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?.()}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
bind:ref
|
|
29
29
|
data-slot="dialog-content"
|
|
30
30
|
class={cn(
|
|
31
|
-
"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-
|
|
31
|
+
"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-lg p-6 text-sm ring-1 duration-100 sm:max-w-lg fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
32
32
|
className
|
|
33
33
|
)}
|
|
34
34
|
{...restProps}
|
|
@@ -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),
|