@kyro-cms/admin 0.9.5 → 0.9.7

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 (35) hide show
  1. package/dist/index.cjs +659 -684
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +54 -51
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.js +660 -685
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/components/ActionBar.tsx +172 -292
  9. package/src/components/Admin.tsx +7 -1
  10. package/src/components/AutoForm.tsx +573 -367
  11. package/src/components/DetailView.tsx +22 -47
  12. package/src/components/GraphQLPlayground.tsx +473 -223
  13. package/src/components/ListView.tsx +1 -1
  14. package/src/components/MediaGallery.tsx +2 -2
  15. package/src/components/RestPlayground.tsx +482 -519
  16. package/src/components/blocks/AccordionBlock.tsx +1 -1
  17. package/src/components/blocks/ArrayBlock.tsx +1 -1
  18. package/src/components/blocks/ChildBlocksTree.tsx +6 -6
  19. package/src/components/blocks/CodeBlock.tsx +1 -1
  20. package/src/components/blocks/FileBlock.tsx +1 -1
  21. package/src/components/blocks/HeroBlock.tsx +1 -1
  22. package/src/components/blocks/ListBlock.tsx +1 -1
  23. package/src/components/blocks/RelationshipBlock.tsx +1 -1
  24. package/src/components/blocks/RichTextBlock.tsx +1 -1
  25. package/src/components/blocks/VideoBlock.tsx +1 -1
  26. package/src/components/fields/BlocksField.tsx +5 -5
  27. package/src/components/fields/RichTextField.tsx +3 -1
  28. package/src/components/ui/SplitButton.tsx +1 -1
  29. package/src/components/ui/Toast.tsx +2 -1
  30. package/src/layouts/AdminLayout.astro +16 -1
  31. package/src/pages/graphql-explorer.astro +7 -51
  32. package/src/pages/graphql.astro +7 -119
  33. package/src/pages/index.astro +4 -63
  34. package/src/pages/rest-playground.astro +3 -29
  35. package/src/styles/main.css +53 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -128,4 +128,4 @@
128
128
  "type": "git",
129
129
  "url": "https://github.com/danielDozie/kyro-cms"
130
130
  }
131
- }
131
+ }
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { IconPlus, IconSend, IconClock, IconArchive, IconUndo, IconCopy, IconEye, IconTrash2 } from "./ui/icons";
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { IconPlus, IconSend, IconClock, IconArchive, IconUndo, IconCopy, IconEye, IconTrash2, IconMoreVertical } from "./ui/icons";
3
3
  import { DropdownItem, DropdownSeparator } from "./ui/Dropdown";
4
4
  import { SplitButton } from "./ui/SplitButton";
5
5
  import { Spinner } from "./ui/Spinner";
@@ -25,6 +25,20 @@ export interface ActionBarProps {
25
25
  updatedAt?: string | null;
26
26
  }
27
27
 
28
+ const STATUS_DOT: Record<string, string> = {
29
+ draft: "bg-[var(--kyro-warning)]",
30
+ published: "bg-[var(--kyro-success)]",
31
+ scheduled: "bg-[var(--kyro-primary)]",
32
+ archived: "bg-[var(--kyro-text-muted)]",
33
+ };
34
+
35
+ const STATUS_COLOR: Record<string, string> = {
36
+ draft: "var(--kyro-warning)",
37
+ published: "var(--kyro-success)",
38
+ scheduled: "var(--kyro-primary)",
39
+ archived: "var(--kyro-text-muted)",
40
+ };
41
+
28
42
  export function ActionBar({
29
43
  status,
30
44
  saveStatus,
@@ -43,322 +57,188 @@ export function ActionBar({
43
57
  }: ActionBarProps) {
44
58
  const view = useAutoFormStore((s) => s.view) || "edit";
45
59
  const setView = useAutoFormStore((s) => s.setView);
60
+ const [moreOpen, setMoreOpen] = useState(false);
61
+ const moreRef = useRef<HTMLDivElement>(null);
62
+
63
+ useEffect(() => {
64
+ const handleClick = (e: MouseEvent) => {
65
+ if (moreRef.current && !moreRef.current.contains(e.target as Node)) setMoreOpen(false);
66
+ };
67
+ document.addEventListener("mousedown", handleClick);
68
+ return () => document.removeEventListener("mousedown", handleClick);
69
+ }, []);
46
70
 
47
71
  const getSaveStatusText = () => {
48
72
  if (saveStatus === "saving") return "Saving...";
49
73
  if (saveStatus === "saved") return "Saved";
50
74
  if (saveStatus === "error") return "Error saving";
51
- if (hasChanges) return "Unsaved changes";
75
+ if (hasChanges) return "Unsaved";
52
76
  return null;
53
77
  };
54
78
 
55
- const getStatusBadge = () => {
56
- const statusConfig = {
57
- draft: {
58
- label: "Draft",
59
- class: "bg-gray-100 text-gray-600 border-gray-200",
60
- icon: <IconPlus className="w-3 h-3" />,
61
- },
62
- published: {
63
- label: "Published",
64
- class: "bg-green-100 text-green-700 border-green-200",
65
- icon: <IconSend className="w-3 h-3" />,
66
- },
67
- scheduled: {
68
- label: "Scheduled",
69
- class: "bg-blue-100 text-blue-700 border-blue-200",
70
- icon: <IconClock className="w-3 h-3" />,
71
- },
72
- archived: {
73
- label: "Archived",
74
- class: "bg-yellow-100 text-yellow-700 border-yellow-200",
75
- icon: <IconArchive className="w-3 h-3" />,
76
- },
77
- };
78
- const config = statusConfig[status];
79
- return (
80
- <span
81
- className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-semibold border ${config.class}`}
82
- >
83
- {config.icon}
84
- {config.label}
85
- </span>
86
- );
87
- };
79
+ const tabs = ["edit", "version", "api"] as const;
80
+ const saveText = getSaveStatusText();
88
81
 
89
- const formatDate = (dateStr: string | null | undefined) => {
90
- if (!dateStr) return "Never";
91
- return new Date(dateStr).toLocaleString();
92
- };
82
+ const iconBtnClass = "p-1.5 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] transition-all shrink-0";
93
83
 
94
84
  return (
95
- <>
96
- {/* ─── MOBILE LAYOUT ─── */}
97
- <div className="md:hidden flex flex-col gap-3 py-3 px-4 bg-[var(--kyro-bg)] border-t border-[var(--kyro-border)]">
98
- {/* Row 1: Back + icon buttons */}
99
- <div className="flex items-center gap-1">
100
- {onBack && (
101
- <button
102
- type="button"
103
- onClick={onBack}
104
- className="p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
105
- >
106
- <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
107
- <path d="M19 12H5M12 19l-7-7 7-7" />
108
- </svg>
109
- </button>
110
- )}
111
- <div className="flex items-center gap-1 ml-auto">
112
- {onPreview && (
113
- <button
114
- type="button"
115
- onClick={onPreview}
116
- className="p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
117
- title="Preview"
118
- >
119
- <IconEye className="w-4 h-4" />
120
- </button>
121
- )}
122
- {onViewHistory && (
123
- <button
124
- type="button"
125
- onClick={onViewHistory}
126
- className="p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
127
- title="View History"
128
- >
129
- <IconClock className="w-4 h-4" />
130
- </button>
131
- )}
132
- {onDuplicate && (
133
- <button
134
- type="button"
135
- onClick={onDuplicate}
136
- className="p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
137
- title="Duplicate"
138
- >
139
- <IconCopy className="w-4 h-4" />
140
- </button>
141
- )}
142
- {onDelete && (
143
- <button
144
- type="button"
145
- onClick={onDelete}
146
- className="p-2 rounded-lg hover:bg-[var(--kyro-danger-bg)] text-[var(--kyro-error)] transition-all"
147
- title="Delete"
148
- >
149
- <IconTrash2 className="w-4 h-4" />
150
- </button>
151
- )}
152
- {onToggleSidebar && (
153
- <button
154
- type="button"
155
- onClick={onToggleSidebar}
156
- className="p-2 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all"
157
- title="Toggle Sidebar"
158
- >
159
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
160
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
161
- <line x1="9" y1="3" x2="9" y2="21" />
162
- </svg>
163
- </button>
164
- )}
165
- </div>
166
- </div>
85
+ <div className="flex items-center gap-1 px-2 py-1.5 bg-[var(--kyro-surface)] border-b border-[var(--kyro-border)] w-full overflow-x-auto">
86
+ {onBack && (
87
+ <button type="button" onClick={onBack} className="p-1 rounded-lg hover:bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] transition-all shrink-0">
88
+ <svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
89
+ <path d="M19 12H5M12 19l-7-7 7-7" />
90
+ </svg>
91
+ </button>
92
+ )}
167
93
 
168
- {/* Row 2: Edit / Version / API tabs */}
169
- <div className="flex items-center gap-1 bg-[var(--kyro-bg-secondary)] p-0.5 rounded-lg border border-[var(--kyro-border)] self-start">
170
- {(["edit", "version", "api"] as const).map((v) => (
171
- <button
172
- key={v}
173
- type="button"
174
- onClick={() => setView(v)}
175
- className={`px-4 py-1.5 text-xs font-bold rounded-md transition-all ${
176
- view === v
177
- ? "bg-[var(--kyro-surface)] shadow-sm border border-[var(--kyro-border)] text-[var(--kyro-text-primary)]"
178
- : "text-[var(--kyro-text-secondary)] opacity-50 hover:opacity-100"
179
- }`}
180
- >
181
- {v === "edit" ? "Edit" : v === "version" ? "Version" : "API"}
182
- </button>
183
- ))}
184
- </div>
94
+ <div className={`w-2 h-2 rounded-full shrink-0 ${STATUS_DOT[status] || "bg-[var(--kyro-text-muted)]"}`} />
185
95
 
186
- {/* Row 3: Status + timestamps */}
187
- <div className="flex items-center gap-2 flex-wrap">
188
- {getStatusBadge()}
189
- {getSaveStatusText() && (
190
- <span
191
- className={`text-xs ${saveStatus === "error" ? "text-[var(--kyro-error)]" : "text-[var(--kyro-text-muted)]"}`}
192
- >
193
- {saveStatus === "saving" ? (
194
- <Spinner size="sm" className="inline mr-1" />
195
- ) : null}
196
- {getSaveStatusText()}
197
- </span>
198
- )}
199
- <span className="text-[10px] text-[var(--kyro-text-muted)] ml-auto">
200
- {updatedAt && `Updated ${formatDate(updatedAt)}`}
201
- {publishedAt && status === "published" && ` · Published ${formatDate(publishedAt)}`}
202
- </span>
203
- </div>
96
+ {saveText && (
97
+ <span className={`text-[10px] whitespace-nowrap max-md:hidden ${saveStatus === "error" ? "text-[var(--kyro-error)]" : "text-[var(--kyro-text-muted)]"}`}>
98
+ {saveStatus === "saving" && <Spinner size="sm" className="inline mr-0.5" />}
99
+ {saveText}
100
+ </span>
101
+ )}
204
102
 
205
- {/* Row 4: Publish + Save */}
206
- <div className="flex items-center gap-2">
207
- {status === "draft" && onPublish && (
208
- <button type="button"
209
- onClick={onPublish}
210
- disabled={saveStatus === "saving"}
211
- className="kyro-btn kyro-btn-primary kyro-btn-md flex items-center gap-2 flex-1 justify-center"
212
- >
213
- <IconSend className="w-4 h-4" />
214
- Publish
215
- </button>
216
- )}
217
- {status === "published" && onUnpublish && (
218
- <button type="button"
219
- onClick={onUnpublish}
220
- disabled={saveStatus === "saving"}
221
- className="kyro-btn kyro-btn-secondary kyro-btn-md flex items-center gap-2 flex-1 justify-center"
222
- >
223
- <IconUndo className="w-4 h-4" />
224
- Unpublish
225
- </button>
226
- )}
227
- <div className="flex-1">
228
- <SplitButton
229
- status={status}
230
- saveStatus={saveStatus}
231
- hasChanges={hasChanges}
232
- onPublish={onSave}
233
- >
234
- {onDuplicate && (
235
- <DropdownItem
236
- icon={<IconCopy className="w-4 h-4" />}
237
- >
238
- Duplicate
239
- </DropdownItem>
103
+ <div className="flex items-center gap-0.5 bg-[var(--kyro-bg-secondary)] p-0.5 rounded-lg border border-[var(--kyro-border)] shrink-0 max-md:hidden">
104
+ {tabs.map((v) => (
105
+ <button key={v} type="button" onClick={() => setView(v)}
106
+ className={`px-2.5 py-1 text-[11px] font-semibold rounded-md transition-all ${
107
+ view === v
108
+ ? "bg-[var(--kyro-surface)] shadow-sm border border-[var(--kyro-border)] text-[var(--kyro-text-primary)]"
109
+ : "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
110
+ }`}
111
+ >
112
+ {v === "edit" ? "Edit" : v === "version" ? "Ver" : "API"}
113
+ </button>
114
+ ))}
115
+ </div>
116
+
117
+ <div className="flex items-center gap-1 ml-auto shrink-0">
118
+ {onPreview && (
119
+ <button type="button" onClick={onPreview} className={`${iconBtnClass} max-md:hidden`} title="Preview">
120
+ <IconEye className="w-3.5 h-3.5" />
121
+ </button>
122
+ )}
123
+ {onViewHistory && (
124
+ <button type="button" onClick={onViewHistory} className={`${iconBtnClass} max-md:hidden`} title="View History">
125
+ <IconClock className="w-3.5 h-3.5" />
126
+ </button>
127
+ )}
128
+ {onDuplicate && (
129
+ <button type="button" onClick={onDuplicate} className={`${iconBtnClass} max-md:hidden`} title="Duplicate">
130
+ <IconCopy className="w-3.5 h-3.5" />
131
+ </button>
132
+ )}
133
+ {onDelete && (
134
+ <button type="button" onClick={onDelete} className={`${iconBtnClass} hover:bg-[var(--kyro-danger-bg)] hover:text-[var(--kyro-danger)] max-md:hidden`} title="Delete">
135
+ <IconTrash2 className="w-3.5 h-3.5" />
136
+ </button>
137
+ )}
138
+ {onToggleSidebar && (
139
+ <button type="button" onClick={onToggleSidebar} className={`${iconBtnClass} max-md:hidden`} title="Toggle Sidebar">
140
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
141
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
142
+ <line x1="9" y1="3" x2="9" y2="21" />
143
+ </svg>
144
+ </button>
145
+ )}
146
+
147
+ {/* Mobile overflow menu */}
148
+ <div ref={moreRef} className="relative md:hidden">
149
+ <button type="button" onClick={() => setMoreOpen(!moreOpen)} className={iconBtnClass} title="More">
150
+ <IconMoreVertical className="w-3.5 h-3.5" />
151
+ </button>
152
+ {moreOpen && (
153
+ <div className="absolute right-0 top-full mt-1 w-48 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg shadow-xl z-50 py-1 max-h-[70vh] overflow-y-auto">
154
+ {saveText && (
155
+ <div className="px-3 py-2 text-[10px] text-[var(--kyro-text-muted)] border-b border-[var(--kyro-border)] flex items-center gap-1.5">
156
+ <div className={`w-1.5 h-1.5 rounded-full ${STATUS_DOT[status]}`} />
157
+ <span className="capitalize">{status}</span>
158
+ <span>·</span>
159
+ {saveStatus === "saving" && <Spinner size="sm" className="inline" />}
160
+ <span>{saveText}</span>
161
+ </div>
240
162
  )}
163
+ <div className="flex gap-1 px-2 py-2 border-b border-[var(--kyro-border)]">
164
+ {tabs.map((v) => (
165
+ <button key={v} type="button" onClick={() => { setView(v); setMoreOpen(false); }}
166
+ className={`px-2.5 py-1 text-[11px] font-semibold rounded-md transition-all flex-1 ${
167
+ view === v
168
+ ? "bg-[var(--kyro-primary)] text-white"
169
+ : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)]"
170
+ }`}
171
+ >
172
+ {v === "edit" ? "Edit" : v === "version" ? "Ver" : "API"}
173
+ </button>
174
+ ))}
175
+ </div>
241
176
  {onViewHistory && (
242
- <DropdownItem
243
- icon={<IconClock className="w-4 h-4" />}
244
- >
245
- View History
246
- </DropdownItem>
177
+ <button type="button" onClick={() => { onViewHistory(); setMoreOpen(false); }} className="w-full flex items-center gap-2 px-3 py-2 text-xs text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
178
+ <IconClock className="w-3.5 h-3.5" /> View History
179
+ </button>
180
+ )}
181
+ {onDuplicate && (
182
+ <button type="button" onClick={() => { onDuplicate(); setMoreOpen(false); }} className="w-full flex items-center gap-2 px-3 py-2 text-xs text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
183
+ <IconCopy className="w-3.5 h-3.5" /> Duplicate
184
+ </button>
247
185
  )}
248
186
  {onPreview && (
249
- <DropdownItem
250
- icon={<IconEye className="w-4 h-4" />}
251
- >
252
- Preview
253
- </DropdownItem>
187
+ <button type="button" onClick={() => { onPreview(); setMoreOpen(false); }} className="w-full flex items-center gap-2 px-3 py-2 text-xs text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] transition-colors">
188
+ <IconEye className="w-3.5 h-3.5" /> Preview
189
+ </button>
254
190
  )}
255
- {(onDuplicate || onViewHistory || onPreview) && <DropdownSeparator />}
256
191
  {onDelete && (
257
- <DropdownItem
258
- onClick={onDelete}
259
- danger
260
- icon={<IconTrash2 className="w-4 h-4" />}
261
- >
262
- Delete
263
- </DropdownItem>
192
+ <div className="border-t border-[var(--kyro-border)] mt-1 pt-1">
193
+ <button type="button" onClick={() => { onDelete(); setMoreOpen(false); }} className="w-full flex items-center gap-2 px-3 py-2 text-xs text-[var(--kyro-danger)] hover:bg-[var(--kyro-danger-bg)] transition-colors">
194
+ <IconTrash2 className="w-3.5 h-3.5" /> Delete
195
+ </button>
196
+ </div>
264
197
  )}
265
- </SplitButton>
266
- </div>
198
+ </div>
199
+ )}
267
200
  </div>
268
- </div>
269
201
 
270
- {/* ─── DESKTOP LAYOUT ─── */}
271
- <div className="hidden md:flex flex-row items-center justify-between gap-4 py-3 px-6 bg-transparent border-0 static z-40">
272
- <div className="flex flex-row items-center gap-4">
273
- <div className="flex items-center gap-2">
274
- {getStatusBadge()}
275
- {getSaveStatusText() && (
276
- <span
277
- className={`text-sm ${saveStatus === "error" ? "text-[var(--kyro-error)]" : "text-[var(--kyro-text-muted)]"}`}
278
- >
279
- {saveStatus === "saving" ? (
280
- <Spinner size="sm" className="inline mr-1" />
281
- ) : null}
282
- {getSaveStatusText()}
283
- </span>
284
- )}
285
- </div>
286
- <div className="text-xs space-y-0.5">
287
- {updatedAt && (
288
- <div className="text-[var(--kyro-text-muted)]">
289
- Updated: {formatDate(updatedAt)}
290
- </div>
291
- )}
292
- {publishedAt && status === "published" && (
293
- <div className="text-[var(--kyro-primary)] font-medium">
294
- Published: {formatDate(publishedAt)}
295
- </div>
296
- )}
297
- </div>
298
- </div>
202
+ <div className="h-4 w-px bg-[var(--kyro-border)] mx-0.5" />
299
203
 
300
- <div className="flex items-center gap-2 flex-wrap">
301
- {status === "draft" && onPublish && (
302
- <button type="button"
303
- onClick={onPublish}
304
- disabled={saveStatus === "saving"}
305
- className="kyro-btn kyro-btn-primary kyro-btn-md flex items-center gap-2"
306
- >
307
- <IconSend className="w-4 h-4" />
308
- Publish
309
- </button>
204
+ {status === "draft" && onPublish && (
205
+ <button type="button" onClick={onPublish} disabled={saveStatus === "saving"}
206
+ className="kyro-btn kyro-btn-primary kyro-btn-sm flex items-center gap-1 shrink-0"
207
+ >
208
+ <IconSend className="w-3.5 h-3.5" />
209
+ <span className="max-md:hidden">Publish</span>
210
+ </button>
211
+ )}
212
+ {status === "published" && onUnpublish && (
213
+ <button type="button" onClick={onUnpublish} disabled={saveStatus === "saving"}
214
+ className="kyro-btn kyro-btn-secondary kyro-btn-sm flex items-center gap-1 shrink-0"
215
+ >
216
+ <IconUndo className="w-3.5 h-3.5" />
217
+ <span className="max-md:hidden">Unpublish</span>
218
+ </button>
219
+ )}
220
+
221
+ <SplitButton
222
+ status={status}
223
+ saveStatus={saveStatus}
224
+ hasChanges={hasChanges}
225
+ onPublish={onSave}
226
+ >
227
+ {onDuplicate && (
228
+ <DropdownItem icon={<IconCopy className="w-4 h-4" />}>Duplicate</DropdownItem>
310
229
  )}
311
- {status === "published" && onUnpublish && (
312
- <button type="button"
313
- onClick={onUnpublish}
314
- disabled={saveStatus === "saving"}
315
- className="kyro-btn kyro-btn-secondary kyro-btn-md flex items-center gap-2"
316
- >
317
- <IconUndo className="w-4 h-4" />
318
- Unpublish
319
- </button>
230
+ {onViewHistory && (
231
+ <DropdownItem icon={<IconClock className="w-4 h-4" />}>View History</DropdownItem>
320
232
  )}
321
-
322
- <SplitButton
323
- status={status}
324
- saveStatus={saveStatus}
325
- hasChanges={hasChanges}
326
- onPublish={onSave}
327
- >
328
- {onDuplicate && (
329
- <DropdownItem
330
- icon={<IconCopy className="w-4 h-4" />}
331
- >
332
- Duplicate
333
- </DropdownItem>
334
- )}
335
- {onViewHistory && (
336
- <DropdownItem
337
- icon={<IconClock className="w-4 h-4" />}
338
- >
339
- View History
340
- </DropdownItem>
341
- )}
342
- {onPreview && (
343
- <DropdownItem
344
- icon={<IconEye className="w-4 h-4" />}
345
- >
346
- Preview
347
- </DropdownItem>
348
- )}
349
- {(onDuplicate || onViewHistory || onPreview) && <DropdownSeparator />}
350
- {onDelete && (
351
- <DropdownItem
352
- onClick={onDelete}
353
- danger
354
- icon={<IconTrash2 className="w-4 h-4" />}
355
- >
356
- Delete
357
- </DropdownItem>
358
- )}
359
- </SplitButton>
360
- </div>
233
+ {onPreview && (
234
+ <DropdownItem icon={<IconEye className="w-4 h-4" />}>Preview</DropdownItem>
235
+ )}
236
+ {(onDuplicate || onViewHistory || onPreview) && <DropdownSeparator />}
237
+ {onDelete && (
238
+ <DropdownItem onClick={onDelete} danger icon={<IconTrash2 className="w-4 h-4" />}>Delete</DropdownItem>
239
+ )}
240
+ </SplitButton>
361
241
  </div>
362
- </>
242
+ </div>
363
243
  );
364
244
  }
@@ -104,7 +104,13 @@ export function Admin({ config, theme = "light", onThemeChange }: AdminProps) {
104
104
  }
105
105
  };
106
106
  window.addEventListener("keydown", handleKeyDown);
107
- return () => window.removeEventListener("keydown", handleKeyDown);
107
+
108
+ (window as { openCommandPalette?: () => void }).openCommandPalette = () => setIsCommandPaletteOpen(true);
109
+
110
+ return () => {
111
+ window.removeEventListener("keydown", handleKeyDown);
112
+ delete (window as { openCommandPalette?: () => void }).openCommandPalette;
113
+ };
108
114
  }, []);
109
115
 
110
116
  const handleLogin = async (data: Record<string, unknown>) => {