@kyro-cms/admin 0.12.19 → 0.12.21
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/dist/index.cjs +24 -24
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -19
- package/package.json +6 -6
- package/src/components/ApiKeysManager.tsx +177 -227
- package/src/components/AuditLogsPage.tsx +7 -7
- package/src/components/AuthorDashboard.tsx +235 -0
- package/src/components/AutoForm.tsx +15 -4
- package/src/components/CustomerDashboard.tsx +213 -0
- package/src/components/Dashboard.tsx +1 -1
- package/src/components/DashboardMetrics.tsx +74 -9
- package/src/components/DashboardQuickSections.tsx +237 -0
- package/src/components/ListView.tsx +3 -3
- package/src/components/MediaGallery.tsx +2 -2
- package/src/components/PluginsManager.tsx +1 -1
- package/src/components/SessionsManager.tsx +15 -15
- package/src/components/Sidebar.astro +564 -116
- package/src/components/UserManagement.tsx +43 -9
- package/src/components/UserMenu.tsx +123 -61
- package/src/components/autoform/AutoFormEditView.tsx +4 -4
- package/src/components/autoform/AutoFormHeader.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +8 -8
- package/src/components/fields/CodeField.tsx +0 -1
- package/src/components/fields/TabsLayout.tsx +5 -2
- package/src/components/ui/CommandPalette.tsx +1 -1
- package/src/components/ui/Dropdown.tsx +71 -16
- package/src/components/ui/PageHeader.tsx +1 -1
- package/src/components/ui/Pagination.tsx +1 -1
- package/src/components/ui/SeoPreview.tsx +25 -21
- package/src/components/ui/Toast.tsx +3 -2
- package/src/components/ui/Toaster.tsx +1 -1
- package/src/components/users/UserDetail.tsx +74 -29
- package/src/components/users/UserForm.tsx +1 -1
- package/src/components/users/UsersList.tsx +2 -2
- package/src/integration.ts +4 -0
- package/src/layouts/AdminLayout.astro +26 -34
- package/src/layouts/AuthLayout.astro +18 -9
- package/src/lib/auth-server.ts +99 -0
- package/src/pages/403.astro +54 -35
- package/src/pages/[collection]/[id].astro +8 -0
- package/src/pages/[collection]/index.astro +7 -0
- package/src/pages/audit/index.astro +4 -1
- package/src/pages/auth/check-email.astro +146 -0
- package/src/pages/auth/forgot-password.astro +95 -0
- package/src/pages/auth/login.astro +29 -12
- package/src/pages/auth/register.astro +38 -12
- package/src/pages/auth/reset-password.astro +125 -0
- package/src/pages/auth/verify-email.astro +87 -0
- package/src/pages/index.astro +220 -121
- package/src/pages/keys.astro +3 -1
- package/src/pages/media.astro +3 -1
- package/src/pages/preview/[collection]/[id].astro +23 -73
- package/src/pages/roles/index.astro +134 -101
- package/src/pages/settings/[slug].astro +43 -3
- package/src/pages/users/[id].astro +5 -1
- package/src/pages/users/index.astro +4 -0
- package/src/pages/users/new.astro +4 -0
- package/src/pages/webhooks.astro +3 -1
- package/src/routes.ts +16 -0
|
@@ -42,9 +42,9 @@ nonAuthCollections
|
|
|
42
42
|
href: `${adminPath}/${col.slug}`,
|
|
43
43
|
label: col.label || col.slug,
|
|
44
44
|
icon: (col.admin?.icon as string) || "collection",
|
|
45
|
-
order: typeof col.admin?.order ===
|
|
45
|
+
order: typeof col.admin?.order === "number" ? col.admin.order : 999,
|
|
46
46
|
};
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
const groupName = (col.admin?.group as string) || "Content";
|
|
49
49
|
if (!groupedCollections.has(groupName)) {
|
|
50
50
|
groupedCollections.set(groupName, []);
|
|
@@ -57,7 +57,14 @@ const dynamicSections = Array.from(groupedCollections.entries()).map(([group, it
|
|
|
57
57
|
items: items.sort((a, b) => (a.order || 999) - (b.order || 999)),
|
|
58
58
|
}));
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
import { getAuthServer } from "../lib/auth-server";
|
|
61
|
+
|
|
62
|
+
const { user: serverUser, permissions: serverPermissions } = await getAuthServer(Astro.request);
|
|
63
|
+
const serverRole = serverUser?.role || "";
|
|
64
|
+
const isServerSuperAdmin = serverRole === "super_admin";
|
|
65
|
+
const isServerAdmin = serverRole === "admin" || isServerSuperAdmin;
|
|
66
|
+
|
|
67
|
+
const navSectionsRaw: { label: string; items: NavItem[] }[] = [
|
|
61
68
|
{
|
|
62
69
|
label: "Dashboard",
|
|
63
70
|
items: [
|
|
@@ -74,7 +81,6 @@ const navSections: { label: string; items: NavItem[] }[] = [
|
|
|
74
81
|
label: "Settings",
|
|
75
82
|
items: [
|
|
76
83
|
{ href: adminPath + "/users", label: "nav.users", icon: "users" },
|
|
77
|
-
{ href: adminPath + "/plugins", label: "nav.plugins", icon: "plugins" },
|
|
78
84
|
{
|
|
79
85
|
href: adminPath + "/settings",
|
|
80
86
|
label: "nav.settings",
|
|
@@ -84,19 +90,63 @@ const navSections: { label: string; items: NavItem[] }[] = [
|
|
|
84
90
|
},
|
|
85
91
|
];
|
|
86
92
|
|
|
93
|
+
const isServerEditor = serverRole === "editor";
|
|
94
|
+
const isServerAuthor = serverRole === "author";
|
|
95
|
+
const isServerCustomer = serverRole === "customer";
|
|
96
|
+
|
|
97
|
+
const navSections = navSectionsRaw
|
|
98
|
+
.map((section) => {
|
|
99
|
+
if (!serverUser) return section;
|
|
100
|
+
const items = section.items.filter((item) => {
|
|
101
|
+
const slug = item.href.split("/").pop() || "";
|
|
102
|
+
if (item.href === adminPath) return true;
|
|
103
|
+
if (slug === "roles") return isServerSuperAdmin;
|
|
104
|
+
if (slug === "plugins" || slug === "keys" || slug === "webhooks" || slug === "settings") return isServerAdmin;
|
|
105
|
+
if (slug === "users") return isServerAdmin || serverPermissions?.collections?.users?.read === true;
|
|
106
|
+
if (slug === "audit" || slug === "audit_logs")
|
|
107
|
+
return isServerAdmin || serverPermissions?.collections?.audit_logs?.read === true;
|
|
108
|
+
if (slug === "media")
|
|
109
|
+
return (
|
|
110
|
+
!isServerCustomer &&
|
|
111
|
+
(isServerAdmin || isServerEditor || isServerAuthor || serverPermissions?.collections?.media?.read === true)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (isServerAdmin) return true;
|
|
115
|
+
if (serverPermissions?.collections?.[slug]) {
|
|
116
|
+
return serverPermissions.collections[slug].read === true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (isServerEditor) {
|
|
120
|
+
return !["users", "audit_logs", "roles", "plugins", "keys", "webhooks", "settings"].includes(slug);
|
|
121
|
+
}
|
|
122
|
+
if (isServerAuthor) {
|
|
123
|
+
return ["posts", "categories", "orders"].includes(slug);
|
|
124
|
+
}
|
|
125
|
+
if (isServerCustomer) {
|
|
126
|
+
return ["orders"].includes(slug);
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
});
|
|
130
|
+
return { ...section, items };
|
|
131
|
+
})
|
|
132
|
+
.filter((section) => section.items.length > 0);
|
|
133
|
+
|
|
87
134
|
const iconAliases: Record<string, string> = {
|
|
88
135
|
collection: "Dot",
|
|
89
136
|
media: "Grid",
|
|
90
137
|
home: "Home",
|
|
91
138
|
users: "Users",
|
|
139
|
+
shield: "Shield",
|
|
92
140
|
plugins: "Blocks",
|
|
141
|
+
key: "Key",
|
|
142
|
+
webhook: "Webhook",
|
|
143
|
+
audit: "FileText",
|
|
93
144
|
settings: "Settings",
|
|
94
145
|
};
|
|
95
146
|
|
|
96
147
|
function isActive(item: NavItem): boolean {
|
|
97
148
|
if (item.href === adminPath) return title === "Dashboard";
|
|
98
|
-
if (item.href.startsWith(adminPath + "/settings"))
|
|
99
|
-
return currentPath.startsWith(adminPath + "/settings");
|
|
149
|
+
if (item.href.startsWith(adminPath + "/settings")) return currentPath.startsWith(adminPath + "/settings");
|
|
100
150
|
if (item.href === adminPath + "/users") return title === "Users";
|
|
101
151
|
if (item.href === adminPath + "/roles") return title === "Permissions";
|
|
102
152
|
if (item.href === adminPath + "/audit") return title === "Audit Logs";
|
|
@@ -105,8 +155,7 @@ function isActive(item: NavItem): boolean {
|
|
|
105
155
|
if (item.href === adminPath + "/plugins") return title === "Plugins";
|
|
106
156
|
if (item.href === adminPath + "/marketplace") return title === "Marketplace";
|
|
107
157
|
// Collections: match /collection-name and /collection-name/* paths
|
|
108
|
-
if (currentPath === item.href || currentPath.startsWith(item.href + "/"))
|
|
109
|
-
return true;
|
|
158
|
+
if (currentPath === item.href || currentPath.startsWith(item.href + "/")) return true;
|
|
110
159
|
return false;
|
|
111
160
|
}
|
|
112
161
|
---
|
|
@@ -115,66 +164,112 @@ function isActive(item: NavItem): boolean {
|
|
|
115
164
|
id="kyro-sidebar"
|
|
116
165
|
class="surface-tile w-[280px] md:w-[320px] flex flex-col flex-shrink-0 overflow-hidden fixed md:static inset-y-0 left-0 z-50 md:z-auto transition-transform duration-300 -translate-x-full md:translate-x-0 h-[100dvh] md:h-auto border-r md:border-r-0 border-[var(--kyro-border)] rounded-r-2xl md:rounded-3xl rounded-l-none md:rounded-l-3xl shadow-2xl md:shadow-none"
|
|
117
166
|
>
|
|
118
|
-
<div
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
<div
|
|
168
|
+
class="sidebar-header px-6 md:px-6 py-6 flex items-center justify-between gap-2 border-b border-[var(--kyro-border)]/50 shrink-0 transition-all duration-300"
|
|
169
|
+
>
|
|
170
|
+
<div class="sidebar-brand flex items-center gap-3 overflow-hidden min-w-0 transition-all duration-300">
|
|
171
|
+
{
|
|
172
|
+
siteLogo || darkLogo ? (
|
|
173
|
+
<div class="flex items-center gap-3">
|
|
174
|
+
<div class="flex items-center flex-shrink-0">
|
|
175
|
+
{siteLogo && (
|
|
176
|
+
<img
|
|
177
|
+
src={siteLogo.url}
|
|
178
|
+
alt={logoAlt}
|
|
179
|
+
style={{
|
|
180
|
+
width: logoWidth ? `${logoWidth}px` : "auto",
|
|
181
|
+
height: logoHeight ? `${logoHeight}px` : "32px",
|
|
182
|
+
objectFit: "contain",
|
|
183
|
+
}}
|
|
184
|
+
class={`rounded-lg ${darkLogo ? "block dark:hidden" : ""}`}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
{darkLogo && (
|
|
188
|
+
<img
|
|
189
|
+
src={darkLogo.url}
|
|
190
|
+
alt={logoAlt}
|
|
191
|
+
style={{
|
|
192
|
+
width: logoWidth ? `${logoWidth}px` : "auto",
|
|
193
|
+
height: logoHeight ? `${logoHeight}px` : "32px",
|
|
194
|
+
objectFit: "contain",
|
|
195
|
+
}}
|
|
196
|
+
class={`rounded-lg ${siteLogo ? "hidden dark:block" : ""}`}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
{showSiteName && (
|
|
201
|
+
<span class="sidebar-text text-xl font-bold tracking-tight text-[var(--kyro-text-primary)] truncate transition-opacity duration-200">
|
|
202
|
+
{siteName}
|
|
203
|
+
</span>
|
|
146
204
|
)}
|
|
147
205
|
</div>
|
|
148
|
-
|
|
149
|
-
|
|
206
|
+
) : (
|
|
207
|
+
<div class="flex items-center gap-3 overflow-hidden">
|
|
208
|
+
<img src="/logo-white.svg" alt="Kyro CMS" class="w-7 h-7 hidden dark:block flex-shrink-0" />
|
|
209
|
+
<img src="/logo.svg" alt="Kyro CMS" class="w-7 h-7 block dark:hidden flex-shrink-0" />
|
|
210
|
+
<span class="sidebar-text text-2xl font-black tracking-tighter text-[var(--kyro-text-primary)] truncate transition-opacity duration-200">
|
|
150
211
|
{siteName}
|
|
151
212
|
</span>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<svg
|
|
213
|
+
</div>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{/* Desktop Panel Toggle Button */}
|
|
219
|
+
<button
|
|
220
|
+
id="sidebar-panel-toggle"
|
|
221
|
+
type="button"
|
|
222
|
+
title="Minimize Sidebar"
|
|
223
|
+
aria-label="Toggle Sidebar"
|
|
224
|
+
class="hidden md:flex items-center justify-center w-7 h-7 rounded-lg border border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface)] transition-all flex-shrink-0 cursor-pointer shadow-sm active:scale-95"
|
|
225
|
+
>
|
|
226
|
+
<svg
|
|
227
|
+
id="sidebar-toggle-icon"
|
|
228
|
+
class="w-4 h-4 transition-transform duration-300"
|
|
229
|
+
fill="none"
|
|
230
|
+
stroke="currentColor"
|
|
231
|
+
viewBox="0 0 24 24"
|
|
232
|
+
>
|
|
233
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7"></path>
|
|
234
|
+
</svg>
|
|
235
|
+
</button>
|
|
236
|
+
|
|
237
|
+
{/* Mobile Close Button */}
|
|
238
|
+
<button
|
|
239
|
+
id="mobile-close-btn"
|
|
240
|
+
class="md:hidden p-2 -mr-2 rounded-lg text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors"
|
|
241
|
+
>
|
|
242
|
+
<svg
|
|
243
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
244
|
+
width="20"
|
|
245
|
+
height="20"
|
|
246
|
+
viewBox="0 0 24 24"
|
|
247
|
+
fill="none"
|
|
248
|
+
stroke="currentColor"
|
|
249
|
+
stroke-width="2"
|
|
250
|
+
stroke-linecap="round"
|
|
251
|
+
stroke-linejoin="round"
|
|
252
|
+
class="lucide lucide-x"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg
|
|
253
|
+
>
|
|
166
254
|
</button>
|
|
167
255
|
</div>
|
|
168
256
|
|
|
169
|
-
<nav class="flex-1 px-4 overflow-y-auto" id="sidebar-nav">
|
|
257
|
+
<nav class="flex-1 px-4 overflow-y-auto overflow-x-hidden" id="sidebar-nav">
|
|
170
258
|
<div class="space-y-4">
|
|
171
259
|
{
|
|
172
260
|
navSections.map((section) => (
|
|
173
261
|
<div class="space-y-2 group/section" data-section={section.label}>
|
|
174
262
|
<div class="pt-4 pb-2">
|
|
175
|
-
<button
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
class="w-full flex items-center justify-between px-6 focus:outline-none sidebar-section-toggle transition-opacity hover:opacity-80"
|
|
266
|
+
>
|
|
176
267
|
<p class="text-[10px] font-medium text-[var(--kyro-text-secondary)] tracking-[0.2em] opacity-40 uppercase">
|
|
177
|
-
{i18next.t(section.label, {
|
|
268
|
+
{i18next.t(section.label, {
|
|
269
|
+
defaultValue: section.label.split(".").pop()?.toUpperCase() || section.label,
|
|
270
|
+
}) ||
|
|
271
|
+
section.label.split(".").pop()?.toUpperCase() ||
|
|
272
|
+
section.label}
|
|
178
273
|
</p>
|
|
179
274
|
<Icons.ChevronDown className="w-3.5 h-3.5 text-[var(--kyro-text-secondary)] opacity-40 transition-transform duration-200 group-data-[collapsed=true]/section:-rotate-90" />
|
|
180
275
|
</button>
|
|
@@ -182,38 +277,36 @@ function isActive(item: NavItem): boolean {
|
|
|
182
277
|
<div class="section-items space-y-1">
|
|
183
278
|
{section.items.map((item) => {
|
|
184
279
|
const slug = item.href.split("/").pop();
|
|
185
|
-
const type = item.href.includes("/settings")
|
|
186
|
-
|
|
187
|
-
|
|
280
|
+
const type = item.href.includes("/settings") ? "global" : "collection";
|
|
281
|
+
const fallback = item.label.split(".").pop() || item.label;
|
|
282
|
+
const capitalized = fallback.charAt(0).toUpperCase() + fallback.slice(1);
|
|
283
|
+
const itemText = i18next.t(item.label, { defaultValue: capitalized }) || capitalized;
|
|
188
284
|
return (
|
|
189
285
|
<a
|
|
190
286
|
href={item.href}
|
|
287
|
+
title={itemText}
|
|
191
288
|
data-nav-item
|
|
192
289
|
data-slug={slug}
|
|
193
290
|
data-type={type}
|
|
194
|
-
|
|
291
|
+
data-is-dashboard={item.href === adminPath ? "true" : "false"}
|
|
292
|
+
class={`flex items-center gap-3 px-4 py-2.5 rounded-xl transition-all font-medium text-sm ${
|
|
195
293
|
item.icon === "collection"
|
|
196
|
-
? currentPath === item.href ||
|
|
197
|
-
currentPath.startsWith(item.href + "/")
|
|
294
|
+
? currentPath === item.href || currentPath.startsWith(item.href + "/")
|
|
198
295
|
? "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] active-nav-item"
|
|
199
296
|
: "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)]"
|
|
200
297
|
: isActive(item)
|
|
201
|
-
? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-
|
|
298
|
+
? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-md active-nav-item"
|
|
202
299
|
: "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)]"
|
|
203
300
|
}`}
|
|
204
301
|
>
|
|
205
|
-
|
|
206
|
-
const iconName = iconAliases[item.icon] || item.icon;
|
|
207
|
-
const Icon = (Icons as any)[iconName] || Icons.Dot;
|
|
208
|
-
return <Icon className="w-4 h-4" strokeWidth={2.5} />;
|
|
209
|
-
})()}
|
|
210
|
-
<span>
|
|
302
|
+
<div class="nav-icon-wrapper flex items-center justify-center flex-shrink-0">
|
|
211
303
|
{(() => {
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
return
|
|
304
|
+
const iconName = iconAliases[item.icon] || item.icon;
|
|
305
|
+
const Icon = (Icons as any)[iconName] || Icons.Dot;
|
|
306
|
+
return <Icon className="w-4 h-4 flex-shrink-0" strokeWidth={2.5} />;
|
|
215
307
|
})()}
|
|
216
|
-
</
|
|
308
|
+
</div>
|
|
309
|
+
<span class="sidebar-text truncate">{itemText}</span>
|
|
217
310
|
</a>
|
|
218
311
|
);
|
|
219
312
|
})}
|
|
@@ -225,96 +318,451 @@ function isActive(item: NavItem): boolean {
|
|
|
225
318
|
</nav>
|
|
226
319
|
|
|
227
320
|
<script>
|
|
321
|
+
import { adminPath } from "../lib/paths";
|
|
322
|
+
|
|
228
323
|
function initSidebar() {
|
|
229
324
|
const sections = document.querySelectorAll("[data-section]");
|
|
230
325
|
sections.forEach((section) => {
|
|
231
326
|
const label = section.getAttribute("data-section");
|
|
232
|
-
const toggleBtn = section.querySelector(
|
|
233
|
-
const itemsContainer = section.querySelector(
|
|
234
|
-
|
|
235
|
-
const isCollapsed = localStorage.getItem(`kyro-sidebar-collapsed-${label}`) ===
|
|
327
|
+
const toggleBtn = section.querySelector(".sidebar-section-toggle");
|
|
328
|
+
const itemsContainer = section.querySelector(".section-items");
|
|
329
|
+
|
|
330
|
+
const isCollapsed = localStorage.getItem(`kyro-sidebar-collapsed-${label}`) === "true";
|
|
236
331
|
if (isCollapsed) {
|
|
237
|
-
section.setAttribute(
|
|
238
|
-
if (itemsContainer) itemsContainer.classList.add(
|
|
332
|
+
section.setAttribute("data-collapsed", "true");
|
|
333
|
+
if (itemsContainer) itemsContainer.classList.add("hidden");
|
|
239
334
|
}
|
|
240
335
|
|
|
241
|
-
const activeItem = section.querySelector(
|
|
336
|
+
const activeItem = section.querySelector(".active-nav-item");
|
|
242
337
|
if (activeItem && isCollapsed) {
|
|
243
|
-
section.removeAttribute(
|
|
244
|
-
if (itemsContainer) itemsContainer.classList.remove(
|
|
245
|
-
localStorage.setItem(`kyro-sidebar-collapsed-${label}`,
|
|
338
|
+
section.removeAttribute("data-collapsed");
|
|
339
|
+
if (itemsContainer) itemsContainer.classList.remove("hidden");
|
|
340
|
+
localStorage.setItem(`kyro-sidebar-collapsed-${label}`, "false");
|
|
246
341
|
}
|
|
247
|
-
|
|
342
|
+
|
|
248
343
|
// Remove old listeners to prevent duplicates
|
|
249
344
|
const newToggleBtn = toggleBtn?.cloneNode(true);
|
|
250
345
|
if (toggleBtn && newToggleBtn && toggleBtn.parentNode) {
|
|
251
346
|
toggleBtn.parentNode.replaceChild(newToggleBtn, toggleBtn);
|
|
252
|
-
newToggleBtn.addEventListener(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
347
|
+
newToggleBtn.addEventListener("click", () => {
|
|
348
|
+
const currentlyCollapsed = section.getAttribute("data-collapsed") === "true";
|
|
349
|
+
if (currentlyCollapsed) {
|
|
350
|
+
section.removeAttribute("data-collapsed");
|
|
351
|
+
if (itemsContainer) itemsContainer.classList.remove("hidden");
|
|
352
|
+
localStorage.setItem(`kyro-sidebar-collapsed-${label}`, "false");
|
|
353
|
+
} else {
|
|
354
|
+
section.setAttribute("data-collapsed", "true");
|
|
355
|
+
if (itemsContainer) itemsContainer.classList.add("hidden");
|
|
356
|
+
localStorage.setItem(`kyro-sidebar-collapsed-${label}`, "true");
|
|
357
|
+
}
|
|
263
358
|
});
|
|
264
359
|
}
|
|
265
360
|
});
|
|
266
361
|
}
|
|
267
362
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
363
|
+
function pruneSidebar(user?: any, permissions?: any) {
|
|
364
|
+
if (!user) {
|
|
365
|
+
// @ts-ignore
|
|
366
|
+
user = window.__kyroAuth?.user;
|
|
367
|
+
// @ts-ignore
|
|
368
|
+
permissions = window.__kyroAuth?.permissions;
|
|
369
|
+
}
|
|
370
|
+
if (!user) return;
|
|
271
371
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
372
|
+
const userRole = user.role || "";
|
|
373
|
+
const isSuperAdmin = userRole === "super_admin";
|
|
374
|
+
const isAdmin = userRole === "admin" || isSuperAdmin;
|
|
375
|
+
const isEditor = userRole === "editor";
|
|
376
|
+
const isAuthor = userRole === "author";
|
|
377
|
+
const isCustomer = userRole === "customer";
|
|
277
378
|
|
|
278
379
|
const navItems = document.querySelectorAll("[data-nav-item]");
|
|
279
380
|
navItems.forEach((item) => {
|
|
280
|
-
const slug = item.getAttribute("data-slug");
|
|
281
|
-
const type = item.getAttribute("data-type");
|
|
381
|
+
const slug = item.getAttribute("data-slug") || "";
|
|
382
|
+
const type = item.getAttribute("data-type") || "";
|
|
282
383
|
|
|
283
384
|
let hasAccess = true;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
385
|
+
|
|
386
|
+
const isDashboard =
|
|
387
|
+
item.getAttribute("data-is-dashboard") === "true" || item.getAttribute("href") === adminPath;
|
|
388
|
+
if (isDashboard) {
|
|
389
|
+
hasAccess = true;
|
|
390
|
+
} else if (slug === "roles") {
|
|
391
|
+
hasAccess = isSuperAdmin;
|
|
392
|
+
} else if (slug === "plugins" || slug === "keys" || slug === "webhooks" || slug === "settings") {
|
|
393
|
+
hasAccess = isAdmin;
|
|
394
|
+
} else if (slug === "users") {
|
|
395
|
+
hasAccess = isAdmin || permissions?.collections?.users?.read === true;
|
|
396
|
+
} else if (slug === "audit" || slug === "audit_logs") {
|
|
397
|
+
hasAccess = isAdmin || permissions?.collections?.audit_logs?.read === true;
|
|
398
|
+
} else if (slug === "media") {
|
|
399
|
+
hasAccess =
|
|
400
|
+
!isCustomer && (isAdmin || isEditor || isAuthor || permissions?.collections?.media?.read === true);
|
|
401
|
+
} else if (type === "collection") {
|
|
402
|
+
if (isAdmin) {
|
|
403
|
+
hasAccess = true;
|
|
404
|
+
} else if (permissions?.collections?.[slug]) {
|
|
405
|
+
hasAccess = permissions.collections[slug].read === true;
|
|
406
|
+
} else if (isEditor) {
|
|
407
|
+
hasAccess = !["users", "audit_logs", "roles", "plugins", "keys", "webhooks", "settings"].includes(slug);
|
|
408
|
+
} else if (isAuthor) {
|
|
409
|
+
hasAccess = ["posts", "categories", "orders"].includes(slug);
|
|
410
|
+
} else if (isCustomer) {
|
|
411
|
+
hasAccess = ["orders"].includes(slug);
|
|
412
|
+
} else {
|
|
413
|
+
hasAccess = false;
|
|
414
|
+
}
|
|
415
|
+
} else if (type === "global") {
|
|
416
|
+
if (!isAdmin) {
|
|
417
|
+
hasAccess = permissions?.globals?.[slug]?.read === true;
|
|
418
|
+
}
|
|
290
419
|
}
|
|
291
420
|
|
|
292
421
|
if (!hasAccess) {
|
|
293
422
|
// @ts-ignore
|
|
294
423
|
item.style.display = "none";
|
|
424
|
+
} else {
|
|
425
|
+
// @ts-ignore
|
|
426
|
+
item.style.display = "";
|
|
295
427
|
}
|
|
296
428
|
});
|
|
297
429
|
|
|
298
430
|
// Hide empty sections after RBAC
|
|
299
431
|
const sections = document.querySelectorAll("[data-section]");
|
|
300
432
|
sections.forEach((section) => {
|
|
301
|
-
const visibleItems = section.querySelectorAll(
|
|
302
|
-
'[data-nav-item]:not([style*="display: none"])'
|
|
303
|
-
);
|
|
433
|
+
const visibleItems = section.querySelectorAll('[data-nav-item]:not([style*="display: none"])');
|
|
304
434
|
if (visibleItems.length === 0) {
|
|
305
435
|
// @ts-ignore
|
|
306
436
|
section.style.display = "none";
|
|
437
|
+
} else {
|
|
438
|
+
// @ts-ignore
|
|
439
|
+
section.style.display = "";
|
|
307
440
|
}
|
|
308
441
|
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const runPruning = () => {
|
|
445
|
+
initSidebar();
|
|
446
|
+
pruneSidebar();
|
|
447
|
+
initPanelToggle();
|
|
448
|
+
initTooltips();
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
function initTooltips() {
|
|
452
|
+
const sidebar = document.getElementById("kyro-sidebar");
|
|
453
|
+
if (!sidebar) return;
|
|
454
|
+
|
|
455
|
+
let tooltipBadge = document.getElementById("sidebar-tooltip-badge");
|
|
456
|
+
if (!tooltipBadge) {
|
|
457
|
+
tooltipBadge = document.createElement("div");
|
|
458
|
+
tooltipBadge.id = "sidebar-tooltip-badge";
|
|
459
|
+
tooltipBadge.className =
|
|
460
|
+
"fixed z-[9999] pointer-events-none opacity-0 transition-opacity duration-150 ease-out px-2.5 py-1.5 text-xs font-semibold rounded-sm bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] border border-[var(--kyro-border)] shadow-md whitespace-nowrap hidden";
|
|
461
|
+
document.body.appendChild(tooltipBadge);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const navItems = document.querySelectorAll("[data-nav-item]");
|
|
465
|
+
navItems.forEach((item) => {
|
|
466
|
+
const handleMouseEnter = () => {
|
|
467
|
+
if (sidebar.getAttribute("data-minimized") !== "true") return;
|
|
468
|
+
const textSpan = item.querySelector(".sidebar-text");
|
|
469
|
+
const label = textSpan?.textContent?.trim() || item.getAttribute("title") || "";
|
|
470
|
+
if (!label || !tooltipBadge) return;
|
|
471
|
+
|
|
472
|
+
tooltipBadge.textContent = label;
|
|
473
|
+
tooltipBadge.classList.remove("hidden");
|
|
474
|
+
|
|
475
|
+
const rect = item.getBoundingClientRect();
|
|
476
|
+
const top = rect.top + rect.height / 2;
|
|
477
|
+
const left = rect.right + 10;
|
|
478
|
+
|
|
479
|
+
tooltipBadge.style.top = `${top}px`;
|
|
480
|
+
tooltipBadge.style.left = `${left}px`;
|
|
481
|
+
tooltipBadge.style.transform = "translateY(-50%)";
|
|
482
|
+
|
|
483
|
+
requestAnimationFrame(() => {
|
|
484
|
+
tooltipBadge?.classList.remove("opacity-0");
|
|
485
|
+
tooltipBadge?.classList.add("opacity-100");
|
|
486
|
+
});
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const handleMouseLeave = () => {
|
|
490
|
+
if (!tooltipBadge) return;
|
|
491
|
+
tooltipBadge.classList.remove("opacity-100");
|
|
492
|
+
tooltipBadge.classList.add("opacity-0");
|
|
493
|
+
setTimeout(() => {
|
|
494
|
+
if (tooltipBadge?.classList.contains("opacity-0")) {
|
|
495
|
+
tooltipBadge.classList.add("hidden");
|
|
496
|
+
}
|
|
497
|
+
}, 150);
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
item.removeEventListener("mouseenter", (item as any)._tpEnter);
|
|
501
|
+
item.removeEventListener("mouseleave", (item as any)._tpLeave);
|
|
502
|
+
(item as any)._tpEnter = handleMouseEnter;
|
|
503
|
+
(item as any)._tpLeave = handleMouseLeave;
|
|
504
|
+
item.addEventListener("mouseenter", handleMouseEnter);
|
|
505
|
+
item.addEventListener("mouseleave", handleMouseLeave);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const navContainer = document.getElementById("sidebar-nav");
|
|
509
|
+
if (navContainer) {
|
|
510
|
+
navContainer.addEventListener(
|
|
511
|
+
"scroll",
|
|
512
|
+
() => {
|
|
513
|
+
if (tooltipBadge) {
|
|
514
|
+
tooltipBadge.classList.remove("opacity-100");
|
|
515
|
+
tooltipBadge.classList.add("opacity-0", "hidden");
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
{ passive: true },
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function initPanelToggle() {
|
|
524
|
+
const sidebar = document.getElementById("kyro-sidebar");
|
|
525
|
+
const toggleBtn = document.getElementById("sidebar-panel-toggle");
|
|
526
|
+
if (!sidebar || !toggleBtn) return;
|
|
527
|
+
|
|
528
|
+
const updateMinimizedUI = (isMin: boolean) => {
|
|
529
|
+
if (isMin) {
|
|
530
|
+
sidebar.setAttribute("data-minimized", "true");
|
|
531
|
+
sidebar.classList.remove("md:w-[280px]", "md:w-[320px]");
|
|
532
|
+
sidebar.classList.add("md:w-[76px]");
|
|
533
|
+
toggleBtn.setAttribute("title", "Expand Sidebar");
|
|
534
|
+
} else {
|
|
535
|
+
sidebar.removeAttribute("data-minimized");
|
|
536
|
+
sidebar.classList.remove("md:w-[76px]");
|
|
537
|
+
sidebar.classList.add("md:w-[280px]");
|
|
538
|
+
toggleBtn.setAttribute("title", "Minimize Sidebar");
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const savedMin = localStorage.getItem("kyro-sidebar-minimized") === "true";
|
|
543
|
+
updateMinimizedUI(savedMin);
|
|
544
|
+
|
|
545
|
+
const newToggleBtn = toggleBtn.cloneNode(true);
|
|
546
|
+
if (toggleBtn.parentNode) {
|
|
547
|
+
toggleBtn.parentNode.replaceChild(newToggleBtn, toggleBtn);
|
|
548
|
+
newToggleBtn.addEventListener("click", () => {
|
|
549
|
+
const isNowMin = sidebar.getAttribute("data-minimized") !== "true";
|
|
550
|
+
localStorage.setItem("kyro-sidebar-minimized", isNowMin ? "true" : "false");
|
|
551
|
+
updateMinimizedUI(isNowMin);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Run on load and view transitions
|
|
557
|
+
runPruning();
|
|
558
|
+
document.addEventListener("astro:page-load", runPruning);
|
|
559
|
+
|
|
560
|
+
// Handle frontend RBAC pruning in the sidebar
|
|
561
|
+
window.addEventListener("kyro:auth-ready", (event) => {
|
|
562
|
+
// @ts-ignore
|
|
563
|
+
const { user, permissions } = event.detail || {};
|
|
564
|
+
pruneSidebar(user, permissions);
|
|
309
565
|
});
|
|
566
|
+
|
|
567
|
+
// Fallback interval check to ensure pruning runs after async auth resolves
|
|
568
|
+
const authCheckInterval = setInterval(() => {
|
|
569
|
+
// @ts-ignore
|
|
570
|
+
if (window.__kyroAuth?.verified) {
|
|
571
|
+
runPruning();
|
|
572
|
+
clearInterval(authCheckInterval);
|
|
573
|
+
}
|
|
574
|
+
}, 100);
|
|
575
|
+
setTimeout(() => clearInterval(authCheckInterval), 5000);
|
|
310
576
|
</script>
|
|
311
577
|
|
|
312
|
-
<
|
|
578
|
+
<style is:global>
|
|
579
|
+
#kyro-sidebar {
|
|
580
|
+
transition:
|
|
581
|
+
width 300ms cubic-bezier(0.4, 0, 0.2, 1),
|
|
582
|
+
transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
583
|
+
overflow-x: hidden !important;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#kyro-sidebar #sidebar-nav {
|
|
587
|
+
overflow-x: hidden !important;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* Collapsed Rail State */
|
|
591
|
+
#kyro-sidebar[data-minimized="true"] {
|
|
592
|
+
width: 76px !important;
|
|
593
|
+
min-width: 76px !important;
|
|
594
|
+
max-width: 76px !important;
|
|
595
|
+
overflow-x: hidden !important;
|
|
596
|
+
}
|
|
597
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-nav,
|
|
598
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-nav > div,
|
|
599
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-nav .group\/section,
|
|
600
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-nav .section-items {
|
|
601
|
+
padding-left: 0 !important;
|
|
602
|
+
padding-right: 0 !important;
|
|
603
|
+
margin-left: 0 !important;
|
|
604
|
+
margin-right: 0 !important;
|
|
605
|
+
width: 100% !important;
|
|
606
|
+
max-width: 100% !important;
|
|
607
|
+
min-width: 0 !important;
|
|
608
|
+
overflow-x: hidden !important;
|
|
609
|
+
box-sizing: border-box !important;
|
|
610
|
+
}
|
|
611
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-nav::-webkit-scrollbar {
|
|
612
|
+
display: none !important;
|
|
613
|
+
width: 0 !important;
|
|
614
|
+
height: 0 !important;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-header {
|
|
618
|
+
padding-left: 0 !important;
|
|
619
|
+
padding-right: 0 !important;
|
|
620
|
+
padding-top: 1rem !important;
|
|
621
|
+
padding-bottom: 0.75rem !important;
|
|
622
|
+
flex-direction: column !important;
|
|
623
|
+
align-items: center !important;
|
|
624
|
+
justify-content: center !important;
|
|
625
|
+
gap: 0.5rem !important;
|
|
626
|
+
}
|
|
627
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-brand {
|
|
628
|
+
justify-content: center !important;
|
|
629
|
+
width: 100% !important;
|
|
630
|
+
margin: 0 auto !important;
|
|
631
|
+
}
|
|
632
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-brand > div {
|
|
633
|
+
justify-content: center !important;
|
|
634
|
+
width: 100% !important;
|
|
635
|
+
margin: 0 auto !important;
|
|
636
|
+
}
|
|
637
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-brand img {
|
|
638
|
+
margin: 0 auto !important;
|
|
639
|
+
width: 28px !important;
|
|
640
|
+
height: 28px !important;
|
|
641
|
+
max-width: 28px !important;
|
|
642
|
+
max-height: 28px !important;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/* Hide text & section headers cleanly */
|
|
646
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-text,
|
|
647
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-section-toggle,
|
|
648
|
+
#kyro-sidebar[data-minimized="true"] [data-section] > div:first-child {
|
|
649
|
+
display: none !important;
|
|
650
|
+
width: 0 !important;
|
|
651
|
+
height: 0 !important;
|
|
652
|
+
opacity: 0 !important;
|
|
653
|
+
overflow: hidden !important;
|
|
654
|
+
visibility: hidden !important;
|
|
655
|
+
font-size: 0 !important;
|
|
656
|
+
line-height: 0 !important;
|
|
657
|
+
margin: 0 !important;
|
|
658
|
+
padding: 0 !important;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/* Center Nav Item Buttons and force Icons to be visible & perfectly centered */
|
|
662
|
+
#kyro-sidebar[data-minimized="true"] a[data-nav-item] {
|
|
663
|
+
display: flex !important;
|
|
664
|
+
align-items: center !important;
|
|
665
|
+
justify-content: center !important;
|
|
666
|
+
gap: 0 !important;
|
|
667
|
+
width: 28px !important;
|
|
668
|
+
height: 28px !important;
|
|
669
|
+
min-width: 28px !important;
|
|
670
|
+
max-width: 28px !important;
|
|
671
|
+
margin-left: auto !important;
|
|
672
|
+
margin-right: auto !important;
|
|
673
|
+
margin-top: 8px !important;
|
|
674
|
+
margin-bottom: 4px !important;
|
|
675
|
+
border-radius: 5px !important;
|
|
676
|
+
padding: 8px !important;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
#kyro-sidebar[data-minimized="true"] .nav-icon-wrapper {
|
|
680
|
+
display: flex !important;
|
|
681
|
+
align-items: center !important;
|
|
682
|
+
justify-content: center !important;
|
|
683
|
+
width: 100% !important;
|
|
684
|
+
height: 100% !important;
|
|
685
|
+
margin: 0 !important;
|
|
686
|
+
padding: 0 !important;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* #kyro-sidebar[data-minimized="true"] .nav-icon-wrapper svg {
|
|
690
|
+
display: block !important;
|
|
691
|
+
width: 20px !important;
|
|
692
|
+
height: 20px !important;
|
|
693
|
+
min-width: 20px !important;
|
|
694
|
+
min-height: 20px !important;
|
|
695
|
+
max-width: 20px !important;
|
|
696
|
+
max-height: 20px !important;
|
|
697
|
+
flex-shrink: 0 !important;
|
|
698
|
+
margin: 0 auto !important;
|
|
699
|
+
visibility: visible !important;
|
|
700
|
+
opacity: 1 !important;
|
|
701
|
+
} */
|
|
702
|
+
|
|
703
|
+
#kyro-sidebar[data-minimized="true"] #sidebar-toggle-icon {
|
|
704
|
+
transform: rotate(180deg);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/* Footer & User Menu Rail alignment for Collapsed State */
|
|
708
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-footer {
|
|
709
|
+
padding: 0.75rem 0 !important;
|
|
710
|
+
margin-top: auto !important;
|
|
711
|
+
width: 100% !important;
|
|
712
|
+
}
|
|
713
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-footer-inner {
|
|
714
|
+
display: flex !important;
|
|
715
|
+
flex-direction: column-reverse !important;
|
|
716
|
+
gap: 0.625rem !important;
|
|
717
|
+
padding: 0 !important;
|
|
718
|
+
align-items: center !important;
|
|
719
|
+
justify-content: center !important;
|
|
720
|
+
background: transparent !important;
|
|
721
|
+
border: none !important;
|
|
722
|
+
box-shadow: none !important;
|
|
723
|
+
width: 100% !important;
|
|
724
|
+
}
|
|
725
|
+
#kyro-sidebar[data-minimized="true"] .theme-toggle-box {
|
|
726
|
+
display: flex !important;
|
|
727
|
+
flex-direction: column !important;
|
|
728
|
+
align-items: center !important;
|
|
729
|
+
justify-content: center !important;
|
|
730
|
+
gap: 0.25rem !important;
|
|
731
|
+
padding: 0.25rem !important;
|
|
732
|
+
width: 32px !important;
|
|
733
|
+
margin: 0 auto !important;
|
|
734
|
+
border-radius: 8px !important;
|
|
735
|
+
}
|
|
736
|
+
#kyro-sidebar[data-minimized="true"] #theme-light-btn,
|
|
737
|
+
#kyro-sidebar[data-minimized="true"] #theme-dark-btn {
|
|
738
|
+
width: 24px !important;
|
|
739
|
+
height: 24px !important;
|
|
740
|
+
padding: 0 !important;
|
|
741
|
+
display: flex !important;
|
|
742
|
+
align-items: center !important;
|
|
743
|
+
justify-content: center !important;
|
|
744
|
+
border-radius: 6px !important;
|
|
745
|
+
}
|
|
746
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-footer-inner > div.flex {
|
|
747
|
+
width: 100% !important;
|
|
748
|
+
justify-content: center !important;
|
|
749
|
+
gap: 0 !important;
|
|
750
|
+
}
|
|
751
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-footer-inner [title*="Account"],
|
|
752
|
+
#kyro-sidebar[data-minimized="true"] .sidebar-footer-inner div[class*="items-center"][class*="justify-center"] {
|
|
753
|
+
width: 32px !important;
|
|
754
|
+
height: 32px !important;
|
|
755
|
+
margin: 0 auto !important;
|
|
756
|
+
border-radius: 8px !important;
|
|
757
|
+
}
|
|
758
|
+
</style>
|
|
759
|
+
|
|
760
|
+
<div class="sidebar-footer px-6 py-4 mt-auto transition-all duration-300">
|
|
313
761
|
<div
|
|
314
|
-
class="flex items-center justify-between p-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-2xl"
|
|
762
|
+
class="sidebar-footer-inner flex items-center justify-between p-2 bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-2xl transition-all duration-300"
|
|
315
763
|
>
|
|
316
764
|
<div
|
|
317
|
-
class="flex p-1 bg-[var(--kyro-bg)] rounded-xl border border-[var(--kyro-border)] shadow-inner"
|
|
765
|
+
class="theme-toggle-box flex p-1 bg-[var(--kyro-bg)] rounded-xl border border-[var(--kyro-border)] shadow-inner transition-all duration-300"
|
|
318
766
|
>
|
|
319
767
|
<button
|
|
320
768
|
id="theme-light-btn"
|