@kyro-cms/admin 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/index.cjs +966 -585
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +29 -9
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +3 -1
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +649 -268
  8. package/dist/index.js.map +1 -1
  9. package/package.json +2 -2
  10. package/src/components/ActionBar.tsx +254 -70
  11. package/src/components/Admin.tsx +10 -17
  12. package/src/components/ApiKeysManager.tsx +1 -0
  13. package/src/components/AuditLogsPage.tsx +3 -3
  14. package/src/components/AutoForm.tsx +51 -34
  15. package/src/components/DetailView.tsx +37 -13
  16. package/src/components/GraphQLPlayground.tsx +460 -224
  17. package/src/components/ListView.tsx +3 -3
  18. package/src/components/LoginPage.tsx +5 -30
  19. package/src/components/MediaGallery.tsx +122 -15
  20. package/src/components/RestPlayground.tsx +443 -519
  21. package/src/components/Sidebar.astro +6 -2
  22. package/src/components/UserManagement.tsx +4 -4
  23. package/src/components/WebhookManager.tsx +4 -4
  24. package/src/components/blocks/AccordionBlock.tsx +1 -1
  25. package/src/components/blocks/ArrayBlock.tsx +1 -1
  26. package/src/components/blocks/ChildBlocksTree.tsx +6 -6
  27. package/src/components/blocks/CodeBlock.tsx +1 -1
  28. package/src/components/blocks/FileBlock.tsx +1 -1
  29. package/src/components/blocks/HeroBlock.tsx +1 -1
  30. package/src/components/blocks/ListBlock.tsx +1 -1
  31. package/src/components/blocks/RelationshipBlock.tsx +1 -1
  32. package/src/components/blocks/RichTextBlock.tsx +1 -1
  33. package/src/components/blocks/VideoBlock.tsx +1 -1
  34. package/src/components/fields/BlocksField.tsx +17 -19
  35. package/src/components/ui/PageHeader.tsx +205 -83
  36. package/src/components/ui/Pagination.tsx +2 -2
  37. package/src/components/ui/SlidePanel.tsx +4 -4
  38. package/src/layouts/AdminLayout.astro +64 -4
  39. package/src/lib/useResourceManager.ts +1 -0
  40. package/src/pages/graphql-explorer.astro +7 -51
  41. package/src/pages/graphql.astro +7 -119
  42. package/src/pages/index.astro +4 -63
  43. package/src/pages/rest-playground.astro +3 -29
  44. package/src/styles/main.css +32 -9
@@ -27,6 +27,104 @@ interface PageHeaderProps {
27
27
  children?: ReactNode;
28
28
  }
29
29
 
30
+ function BackButton({ back }: { back: NonNullable<PageHeaderProps["back"]> }) {
31
+ if (back.href) {
32
+ return (
33
+ <a
34
+ href={back.href}
35
+ onClick={(e) => {
36
+ if (back.onClick) {
37
+ e.preventDefault();
38
+ back.onClick();
39
+ }
40
+ }}
41
+ className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
42
+ >
43
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
44
+ <path d="M19 12H5M12 19l-7-7 7-7" />
45
+ </svg>
46
+ </a>
47
+ );
48
+ }
49
+ return (
50
+ <button
51
+ type="button"
52
+ onClick={back.onClick}
53
+ className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
54
+ >
55
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
56
+ <path d="M19 12H5M12 19l-7-7 7-7" />
57
+ </svg>
58
+ </button>
59
+ );
60
+ }
61
+
62
+ function DesktopBreadcrumbs({ breadcrumbs }: { breadcrumbs: Breadcrumb[] }) {
63
+ return breadcrumbs?.map((crumb: Breadcrumb, i: number) => (
64
+ <React.Fragment key={i}>
65
+ {i > 0 && <span className="opacity-20 text-[10px]">/</span>}
66
+ {crumb.href || crumb.onClick ? (
67
+ <a
68
+ href={crumb.href}
69
+ onClick={(e) => {
70
+ if (crumb.onClick) {
71
+ e.preventDefault();
72
+ crumb.onClick();
73
+ }
74
+ }}
75
+ className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] transition-all"
76
+ >
77
+ {crumb.label}
78
+ </a>
79
+ ) : (
80
+ <span className="text-[10px] font-bold tracking-widest opacity-40">
81
+ {crumb.label}
82
+ </span>
83
+ )}
84
+ </React.Fragment>
85
+ ));
86
+ }
87
+
88
+ function ActionsSlot({ actions }: { actions: NonNullable<PageHeaderProps["actions"]> }) {
89
+ if (Array.isArray(actions)) {
90
+ return (
91
+ <div className="flex items-center gap-3">
92
+ {actions.map((act, i) => (
93
+ <button
94
+ key={i}
95
+ type="button"
96
+ onClick={act.onClick}
97
+ className={`flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${
98
+ act.variant === "outline"
99
+ ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
100
+ : act.variant === "ghost"
101
+ ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
102
+ : "kyro-btn-primary hover:opacity-90"
103
+ } ${act.className || ""}`}
104
+ >
105
+ {act.icon && <act.icon className="w-4 h-4" />}
106
+ {act.label}
107
+ </button>
108
+ ))}
109
+ </div>
110
+ );
111
+ }
112
+ return <>{actions}</>;
113
+ }
114
+
115
+ function SingleAction({ action }: { action: NonNullable<PageHeaderProps["action"]> }) {
116
+ return (
117
+ <button
118
+ type="button"
119
+ onClick={action.onClick}
120
+ className={`kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10 w-full lg:w-auto justify-center ${action.className || ""}`}
121
+ >
122
+ {action.icon && <action.icon className="w-4 h-4" />}
123
+ {action.label}
124
+ </button>
125
+ );
126
+ }
127
+
30
128
  export function PageHeader({
31
129
  title,
32
130
  description,
@@ -38,66 +136,75 @@ export function PageHeader({
38
136
  actions,
39
137
  children,
40
138
  }: PageHeaderProps) {
139
+ const lastBreadcrumb = breadcrumbs?.[breadcrumbs.length - 1];
140
+
41
141
  return (
42
- <div className="flex flex-col lg:flex-row lg:items-center surface-tile justify-between gap-6 pt-4 mb-8">
43
- <div className="min-w-0 flex-1">
44
- {/* Breadcrumbs / Back */}
142
+ <div className="surface-tile px-3 md:px-6 py-3 md:pt-4 mb-4 md:mb-8">
143
+ {/* ─── MOBILE ─── */}
144
+ <div className="md:hidden space-y-2">
45
145
  {(breadcrumbs || back) && (
46
- <div className="flex items-center gap-2 mb-3">
47
- {back && (
48
- <a
49
- href={back.href}
50
- onClick={(e) => {
51
- if (back.onClick) {
52
- e.preventDefault();
53
- back.onClick();
54
- }
55
- }}
56
- className="p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
57
- >
58
- <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
59
- <path d="M19 12H5M12 19l-7-7 7-7" />
146
+ <div className="flex items-center gap-2">
147
+ {back && <BackButton back={back} />}
148
+ <details className="group [&::-webkit-details-marker]:hidden flex-1 min-w-0">
149
+ <summary className="flex items-center gap-2 cursor-pointer list-none">
150
+ <span className="flex-1 text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] truncate">
151
+ {lastBreadcrumb?.label || ""}
152
+ </span>
153
+ <svg className="w-3 h-3 text-[var(--kyro-text-secondary)] opacity-40 group-open:rotate-180 transition-transform" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
154
+ <path d="M6 9l6 6 6-6" />
60
155
  </svg>
61
- </a>
62
- )}
63
- {breadcrumbs?.map((crumb: Breadcrumb, i: number) => (
64
- <React.Fragment key={i}>
65
- {i > 0 && <span className="opacity-20 text-[10px]">/</span>}
66
- {crumb.href || crumb.onClick ? (
67
- <a
68
- href={crumb.href}
69
- onClick={(e) => {
70
- if (crumb.onClick) {
71
- e.preventDefault();
72
- crumb.onClick();
73
- }
74
- }}
75
- className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] transition-all"
76
- >
77
- {crumb.label}
78
- </a>
79
- ) : (
80
- <span className="text-[10px] font-bold tracking-widest opacity-40">
81
- {crumb.label}
82
- </span>
156
+ </summary>
157
+ <div className="mt-2 pt-2 border-t border-[var(--kyro-border)] space-y-2">
158
+ {breadcrumbs && (
159
+ <div className="flex items-center gap-2">
160
+ {breadcrumbs.map((crumb: Breadcrumb, i: number) => (
161
+ <React.Fragment key={i}>
162
+ {i > 0 && <span className="opacity-20 text-[10px]">/</span>}
163
+ {crumb.href || crumb.onClick ? (
164
+ <a
165
+ href={crumb.href}
166
+ onClick={(e) => {
167
+ if (crumb.onClick) { e.preventDefault(); crumb.onClick(); }
168
+ }}
169
+ className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)] transition-all"
170
+ >
171
+ {crumb.label}
172
+ </a>
173
+ ) : (
174
+ <span className="text-[10px] font-bold tracking-widest opacity-40">{crumb.label}</span>
175
+ )}
176
+ </React.Fragment>
177
+ ))}
178
+ </div>
83
179
  )}
84
- </React.Fragment>
85
- ))}
180
+ {metadata && (
181
+ <div className="flex items-center gap-2 flex-wrap">
182
+ {metadata.map((item, i) => (
183
+ <React.Fragment key={i}>{item}</React.Fragment>
184
+ ))}
185
+ </div>
186
+ )}
187
+ {children}
188
+ </div>
189
+ </details>
86
190
  </div>
87
191
  )}
88
192
 
89
- <div className="flex items-center gap-3">
90
- {Icon && <Icon className="w-6 h-6 text-[var(--kyro-primary)]" />}
193
+ <div className="flex items-center gap-2">
194
+ {Icon && <Icon className="w-5 h-5 text-[var(--kyro-primary)] shrink-0" />}
91
195
  {title && (
92
- <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
196
+ <h1 className="text-lg font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
93
197
  {title}
94
198
  </h1>
95
199
  )}
200
+ {metadata && !description && (
201
+ <span className="h-2 w-2 rounded-full bg-[var(--kyro-primary)] shrink-0" />
202
+ )}
96
203
  </div>
97
204
 
98
205
  {description && (
99
- <div className="flex items-center gap-2 mt-1">
100
- <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1">
206
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
207
+ <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1 min-w-0 text-xs">
101
208
  {description}
102
209
  </p>
103
210
  {metadata && (
@@ -110,49 +217,64 @@ export function PageHeader({
110
217
  ))}
111
218
  </div>
112
219
  )}
113
- {children}
114
220
  </div>
115
221
  )}
116
222
  </div>
117
223
 
118
- <div className="flex items-center gap-3">
119
- {actions && (
120
- Array.isArray(actions) ? (
121
- <div className="flex items-center gap-3">
122
- {actions.map((act, i) => (
123
- <button
124
- key={i}
125
- type="button"
126
- onClick={act.onClick}
127
- className={`flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${
128
- act.variant === "outline"
129
- ? "border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"
130
- : act.variant === "ghost"
131
- ? "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] shadow-none"
132
- : "kyro-btn-primary hover:opacity-90"
133
- } ${act.className || ""}`}
134
- >
135
- {act.icon && <act.icon className="w-4 h-4" />}
136
- {act.label}
137
- </button>
138
- ))}
224
+ {/* ─── DESKTOP ─── */}
225
+ <div className="hidden md:flex md:flex-row md:items-center justify-between gap-6">
226
+ <div className="min-w-0 flex-1">
227
+ {(breadcrumbs || back) && (
228
+ <div className="flex items-center gap-2 mb-3">
229
+ {back && <BackButton back={back} />}
230
+ {breadcrumbs && <DesktopBreadcrumbs breadcrumbs={breadcrumbs} />}
139
231
  </div>
140
- ) : (
141
- actions
142
- )
143
- )}
144
- {action && (
145
- <button
146
- type="button"
147
- onClick={action.onClick}
148
- className={`kyro-btn kyro-btn-primary flex items-center gap-2 px-6 py-2.5 rounded-xl font-bold text-sm hover:opacity-90 transition-all shadow-lg shadow-[var(--kyro-primary)]/10 ${action.className || ""}`}
149
- >
150
- {action.icon && <action.icon className="w-4 h-4" />}
151
- {action.label}
152
- </button>
153
- )}
232
+ )}
233
+
234
+ <div className="flex items-center gap-3">
235
+ {Icon && <Icon className="w-6 h-6 text-[var(--kyro-primary)]" />}
236
+ {title && (
237
+ <h1 className="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)] truncate">
238
+ {title}
239
+ </h1>
240
+ )}
241
+ </div>
242
+
243
+ {(description || metadata) && (
244
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1 mt-1">
245
+ {description && (
246
+ <p className="text-[var(--kyro-text-secondary)] font-medium opacity-60 line-clamp-1 min-w-0">
247
+ {description}
248
+ </p>
249
+ )}
250
+ {metadata && (
251
+ <div className="flex items-center gap-2">
252
+ {metadata.map((item: ReactNode, i: number) => (
253
+ <React.Fragment key={i}>
254
+ {i === 0 && (description || i > 0) && <span className="opacity-20 ml-1">·</span>}
255
+ {item}
256
+ </React.Fragment>
257
+ ))}
258
+ </div>
259
+ )}
260
+ {children}
261
+ </div>
262
+ )}
263
+ </div>
264
+
265
+ <div className="flex items-center gap-3 flex-wrap shrink-0">
266
+ {actions && <ActionsSlot actions={actions} />}
267
+ {action && <SingleAction action={action} />}
268
+ </div>
154
269
  </div>
270
+
271
+ {/* mobile actions */}
272
+ {(actions || action) && (
273
+ <div className="md:hidden flex items-center gap-2 mt-3 pt-3 border-t border-[var(--kyro-border)]">
274
+ {action && <SingleAction action={action} />}
275
+ {actions && <ActionsSlot actions={actions} />}
276
+ </div>
277
+ )}
155
278
  </div>
156
279
  );
157
280
  }
158
-
@@ -13,7 +13,7 @@ export function Pagination({ page, totalPages, totalDocs, limit, onPageChange, o
13
13
  if (totalPages <= 1) return null;
14
14
 
15
15
  return (
16
- <div className="flex items-center justify-between px-4 py-3 border-t border-[var(--kyro-border)]">
16
+ <div className="flex flex-col sm:flex-row items-center justify-between gap-3 px-4 py-3 border-t border-[var(--kyro-border)]">
17
17
  {totalDocs !== undefined && limit ? (
18
18
  <span className="text-xs text-[var(--kyro-text-secondary)] font-medium">
19
19
  Showing {(page - 1) * limit + 1} to {Math.min(page * limit, totalDocs)} of {totalDocs}
@@ -21,7 +21,7 @@ export function Pagination({ page, totalPages, totalDocs, limit, onPageChange, o
21
21
  ) : (
22
22
  <span />
23
23
  )}
24
- <div className="flex items-center gap-2">
24
+ <div className="flex items-center gap-2 flex-wrap justify-center">
25
25
  {onLimitChange && (
26
26
  <select
27
27
  value={limit}
@@ -47,10 +47,10 @@ export function SlidePanel({
47
47
  }, [open, onClose]);
48
48
 
49
49
  const widthClasses = {
50
- sm: "w-[320px]",
51
- md: "w-[400px]",
52
- lg: "w-[550px]",
53
- xl: "w-[700px]",
50
+ sm: "w-full sm:w-[320px]",
51
+ md: "w-full sm:w-[400px]",
52
+ lg: "w-full sm:w-[550px]",
53
+ xl: "w-full sm:w-[700px]",
54
54
  };
55
55
 
56
56
  if (!open || !hydrated) return null;
@@ -177,17 +177,64 @@ if (includeSiteName) {
177
177
  })();
178
178
  </script>
179
179
  </head>
180
- <body class="bg-[var(--kyro-bg)] antialiased text-[var(--kyro-text-primary)]">
180
+ <body class="bg-[var(--kyro-bg)] antialiased text-[var(--kyro-text-primary)] overflow-x-hidden overflow-y-auto">
181
181
  <div id="kyro-user-data" data-user=""></div>
182
- <div class="flex h-screen p-6 gap-6 overflow-hidden">
182
+
183
+ <!-- Mobile Sidebar Backdrop -->
184
+ <div id="mobile-sidebar-backdrop" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 hidden md:hidden transition-opacity opacity-0 duration-300"></div>
185
+
186
+ <div class="flex h-[100dvh] md:h-screen p-0 md:p-6 gap-0 md:gap-6 overflow-hidden w-full relative">
183
187
  <Sidebar title={title} />
184
188
 
185
189
  <!-- Main Content Column -->
186
- <main class="flex-1 flex flex-col gap-6 overflow-hidden">
187
- <slot />
190
+ <main class="flex-1 flex flex-col gap-0 md:gap-6 overflow-hidden relative w-full h-full max-w-full">
191
+ <!-- Mobile Header -->
192
+ <header class="md:hidden flex items-center justify-between p-4 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0 z-30 shadow-sm">
193
+ <div class="flex items-center gap-3">
194
+ <button id="mobile-menu-btn" class="p-2 -ml-2 rounded-lg text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
195
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
196
+ </button>
197
+ <span class="font-bold text-lg">{title}</span>
198
+ </div>
199
+ </header>
200
+
201
+ <div class="flex-1 overflow-y-auto p-4 md:p-0">
202
+ <slot />
203
+ </div>
188
204
  </main>
189
205
  </div>
190
206
 
207
+ <!-- Mobile Sidebar Logic -->
208
+ <script is:inline>
209
+ const menuBtn = document.getElementById("mobile-menu-btn");
210
+ const closeBtn = document.getElementById("mobile-close-btn");
211
+ const backdrop = document.getElementById("mobile-sidebar-backdrop");
212
+ const sidebar = document.getElementById("kyro-sidebar");
213
+
214
+ const toggleSidebar = () => {
215
+ if (!sidebar) return;
216
+ const isOpen = sidebar.classList.contains("translate-x-0");
217
+ const fab = document.getElementById("kyro-fab");
218
+ if (isOpen) {
219
+ sidebar.classList.remove("translate-x-0");
220
+ sidebar.classList.add("-translate-x-full");
221
+ backdrop?.classList.remove("opacity-100");
222
+ setTimeout(() => backdrop?.classList.add("hidden"), 300);
223
+ fab?.classList.remove("fab-hidden");
224
+ } else {
225
+ sidebar.classList.remove("-translate-x-full");
226
+ sidebar.classList.add("translate-x-0");
227
+ backdrop?.classList.remove("hidden");
228
+ setTimeout(() => backdrop?.classList.add("opacity-100"), 10);
229
+ fab?.classList.add("fab-hidden");
230
+ }
231
+ };
232
+
233
+ menuBtn?.addEventListener("click", toggleSidebar);
234
+ closeBtn?.addEventListener("click", toggleSidebar);
235
+ backdrop?.addEventListener("click", toggleSidebar);
236
+ </script>
237
+
191
238
  <!-- Logout Confirmation Modal -->
192
239
  <div
193
240
  id="logout-modal"
@@ -246,6 +293,19 @@ if (includeSiteName) {
246
293
  <!-- Toast Notifications (React) -->
247
294
  <Toaster client:only="react" />
248
295
 
296
+ <!-- Floating Command Palette Trigger -->
297
+ <button
298
+ id="kyro-fab"
299
+ type="button"
300
+ onclick="window.openCommandPalette?.()"
301
+ class="fixed bottom-6 right-6 z-50 w-12 h-12 flex md:hidden items-center justify-center rounded-2xl bg-[var(--kyro-primary)] text-white shadow-lg shadow-[var(--kyro-primary)]/30 hover:shadow-xl hover:shadow-[var(--kyro-primary)]/40 hover:scale-110 active:scale-95 transition-all duration-200"
302
+ title="Open command palette (Cmd+K)"
303
+ >
304
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
305
+ <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
306
+ </svg>
307
+ </button>
308
+
249
309
  <!-- Theme UI Logic -->
250
310
  <script is:inline define:vars={{ adminPath, apiPath }}>
251
311
  const lightBtn = document.getElementById("theme-light-btn");
@@ -50,6 +50,7 @@ export function useResourceManager<T extends { id: string }>(
50
50
  await apiDelete(`${options.endpoint}/${id}`);
51
51
  setItems((prev) => prev.filter((item) => item.id !== id));
52
52
  options.onSuccess?.("delete", id);
53
+ toast.success(`${resourceName} deleted`);
53
54
  } catch (e: unknown) {
54
55
  const message = e instanceof Error ? e.message : `Failed to delete ${resourceName}`;
55
56
  toast.error(message);
@@ -1,59 +1,15 @@
1
1
  ---
2
2
  import AdminLayout from "../layouts/AdminLayout.astro";
3
3
  import { GraphQLPlayground } from "../components/GraphQLPlayground";
4
-
5
- import { adminPath, apiPath } from "../lib/paths";
4
+ import { apiPath } from "../lib/paths";
6
5
  ---
7
6
 
8
7
  <AdminLayout title="GraphQL Explorer">
9
- <div class="flex-1 overflow-hidden">
10
- <!-- Header -->
11
- <div class="mb-6 surface-tile">
12
- <div class="flex items-center justify-between mb-4">
13
- <div>
14
- <h1
15
- class="text-xl font-bold tracking-tighter text-[var(--kyro-text-primary)]"
16
- >
17
- GraphQL Explorer
18
- </h1>
19
- <p
20
- class="text-[var(--kyro-text-secondary)] font-bold mt-2 text-sm tracking-wider"
21
- >
22
- Schema documentation and type explorer
23
- </p>
24
- </div>
25
- <div class="flex items-center gap-4">
26
- <a
27
- href={`${adminPath}/graphql`}
28
- class="flex items-center gap-2 px-4 py-2 bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-primary)] rounded-lg font-bold text-sm hover:bg-[var(--kyro-surface)] transition-all border border-[var(--kyro-border)]"
29
- >
30
- <svg
31
- class="w-4 h-4"
32
- fill="none"
33
- stroke="currentColor"
34
- viewBox="0 0 24 24"
35
- >
36
- <path
37
- stroke-linecap="round"
38
- stroke-linejoin="round"
39
- stroke-width="2"
40
- d="M13 10V3L4 14h7v7l9-11h-7z"></path>
41
- </svg>
42
- Playground
43
- </a>
44
- </div>
45
- </div>
46
- </div>
47
-
48
- <!-- Explorer Container -->
49
- <div class="h-[calc(100vh-200px)] surface-tile overflow-hidden">
50
- <div class="surface-tile h-full overflow-hidden">
51
- <GraphQLPlayground
52
- client:load
53
- endpoint={`${apiPath}/graphql`}
54
- initialShowDocs={true}
55
- />
56
- </div>
57
- </div>
8
+ <div class="flex-1 overflow-hidden h-[calc(100vh-64px)] md:h-[calc(100vh-80px)]">
9
+ <GraphQLPlayground
10
+ client:load
11
+ endpoint={`${apiPath}/graphql`}
12
+ initialShowDocs={true}
13
+ />
58
14
  </div>
59
15
  </AdminLayout>