@kyro-cms/admin 0.9.0 → 0.9.2

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 (114) hide show
  1. package/dist/index.cjs +11715 -11292
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +67 -65
  4. package/dist/index.css.map +1 -1
  5. package/dist/index.d.cts +564 -0
  6. package/dist/index.d.ts +11 -10
  7. package/dist/index.js +11326 -10912
  8. package/dist/index.js.map +1 -1
  9. package/package.json +16 -12
  10. package/src/components/ActionBar.tsx +25 -161
  11. package/src/components/Admin.tsx +2 -4
  12. package/src/components/ApiKeysManager.tsx +5 -5
  13. package/src/components/AuditLogsPage.tsx +2 -13
  14. package/src/components/AutoForm.tsx +572 -461
  15. package/src/components/BrandingHub.tsx +7 -4
  16. package/src/components/CreateView.tsx +2 -0
  17. package/src/components/DetailView.tsx +52 -65
  18. package/src/components/DeveloperCenter.tsx +8 -6
  19. package/src/components/FieldRenderer.tsx +94 -19
  20. package/src/components/ListView.tsx +57 -216
  21. package/src/components/MediaGallery.tsx +334 -367
  22. package/src/components/PluginsManager.tsx +197 -70
  23. package/src/components/RestPlayground.tsx +59 -52
  24. package/src/components/SessionsManager.tsx +1 -1
  25. package/src/components/SettingsPage.tsx +22 -0
  26. package/src/components/Sidebar.astro +13 -41
  27. package/src/components/UserManagement.tsx +153 -15
  28. package/src/components/UserMenu.tsx +30 -4
  29. package/src/components/VersionHistoryPanel.tsx +112 -119
  30. package/src/components/WebhookManager.tsx +6 -4
  31. package/src/components/blocks/ArrayBlock.tsx +6 -23
  32. package/src/components/blocks/BlockEditModal.tsx +82 -309
  33. package/src/components/blocks/CardBlock.tsx +35 -0
  34. package/src/components/blocks/ChildBlocksTree.tsx +57 -31
  35. package/src/components/blocks/GenericBlock.tsx +44 -0
  36. package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
  37. package/src/components/blocks/HeroBlock.tsx +5 -14
  38. package/src/components/blocks/RichTextBlock.tsx +5 -5
  39. package/src/components/blocks/index.ts +5 -3
  40. package/src/components/fields/AccordionField.tsx +2 -2
  41. package/src/components/fields/ArrayField.tsx +1 -1
  42. package/src/components/fields/ArrayLayout.tsx +120 -29
  43. package/src/components/fields/BlocksField.tsx +433 -55
  44. package/src/components/fields/CardField.tsx +73 -0
  45. package/src/components/fields/CheckboxField.tsx +7 -3
  46. package/src/components/fields/DateField.tsx +4 -1
  47. package/src/components/fields/GroupLayout.tsx +2 -2
  48. package/src/components/fields/HeadingSubheadingField.tsx +43 -0
  49. package/src/components/fields/ListField.tsx +2 -2
  50. package/src/components/fields/NumberField.tsx +4 -1
  51. package/src/components/fields/RelationshipBlockField.tsx +2 -3
  52. package/src/components/fields/RelationshipField.tsx +155 -90
  53. package/src/components/fields/RichTextField.tsx +781 -0
  54. package/src/components/fields/SecretField.tsx +102 -0
  55. package/src/components/fields/SelectField.tsx +19 -6
  56. package/src/components/fields/TabsLayout.tsx +19 -9
  57. package/src/components/fields/TextField.tsx +4 -1
  58. package/src/components/fields/UploadField.tsx +122 -56
  59. package/src/components/fields/extensions/blockComponents.tsx +103 -174
  60. package/src/components/fields/extensions/blocksStore.ts +8 -1
  61. package/src/components/fields/index.ts +4 -2
  62. package/src/components/fix_imports.cjs +23 -0
  63. package/src/components/fix_imports2.cjs +19 -0
  64. package/src/components/replace_svgs.cjs +63 -0
  65. package/src/components/ui/Dropdown.tsx +7 -2
  66. package/src/components/ui/Modal.tsx +24 -27
  67. package/src/components/ui/PageHeader.tsx +5 -5
  68. package/src/components/ui/PromptModal.tsx +2 -10
  69. package/src/components/ui/SlidePanel.tsx +10 -13
  70. package/src/components/ui/SplitButton.tsx +107 -0
  71. package/src/components/ui/Toaster.tsx +0 -1
  72. package/src/components/ui/icons.tsx +110 -109
  73. package/src/components/users/UserDetail.tsx +79 -16
  74. package/src/components/users/UsersList.tsx +8 -85
  75. package/src/hooks/useAutoFormState.ts +187 -196
  76. package/src/hooks/useQueue.ts +60 -0
  77. package/src/integration.ts +148 -46
  78. package/src/kyro-cms.d.ts +7 -2
  79. package/src/layouts/AdminLayout.astro +22 -2
  80. package/src/layouts/AuthLayout.astro +67 -7
  81. package/src/lib/autoform-store.ts +90 -53
  82. package/src/lib/change-source.ts +9 -0
  83. package/src/lib/config.ts +104 -8
  84. package/src/lib/globals.ts +48 -11
  85. package/src/lib/normalize-upload-fields.ts +41 -0
  86. package/src/lib/paths.ts +2 -2
  87. package/src/lib/resolve-field-value.ts +110 -0
  88. package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
  89. package/src/lib/shim/use-sync-external-store.js +1 -0
  90. package/src/lib/stores/index.ts +1 -0
  91. package/src/lib/useResourceManager.ts +4 -4
  92. package/src/lib/vite-shim-plugin.ts +100 -0
  93. package/src/pages/[collection]/[id].astro +1 -1
  94. package/src/pages/auth/register.astro +5 -2
  95. package/src/pages/preview/[collection]/[id].astro +4 -4
  96. package/src/pages/settings/[slug].astro +2 -2
  97. package/src/styles/main.css +60 -54
  98. package/README.md +0 -46
  99. package/dist/EditorClient-Q23UXR37.cjs +0 -468
  100. package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
  101. package/dist/EditorClient-T5PASFNR.js +0 -466
  102. package/dist/EditorClient-T5PASFNR.js.map +0 -1
  103. package/dist/chunk-3BGDYKTD.cjs +0 -348
  104. package/dist/chunk-3BGDYKTD.cjs.map +0 -1
  105. package/dist/chunk-EEFXLQVT.js +0 -3
  106. package/dist/chunk-EEFXLQVT.js.map +0 -1
  107. package/src/components/blocks/ButtonBlock.tsx +0 -64
  108. package/src/components/blocks/ColumnsBlock.tsx +0 -55
  109. package/src/components/blocks/DividerBlock.tsx +0 -43
  110. package/src/components/blocks/LinkBlock.tsx +0 -65
  111. package/src/components/blocks/VStackBlock.tsx +0 -29
  112. package/src/components/fields/EditorClient.tsx +0 -535
  113. package/src/components/fields/PortableTextField.tsx +0 -155
  114. package/src/components/fields/PortableTextRenderer.tsx +0 -68
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import { apiGet, apiPatch } from "../lib/api";
3
+ import { toast } from "../lib/stores";
3
4
  import {
4
5
  Palette,
5
6
  Tag,
@@ -25,7 +26,7 @@ export function BrandingHub() {
25
26
  useEffect(() => {
26
27
  const fetchBranding = async () => {
27
28
  try {
28
- const result = await apiGet("/api/globals/site");
29
+ const result = await apiGet("/api/globals/site-settings");
29
30
  const data = result.data || result;
30
31
  if (data && Object.keys(data).length > 0) {
31
32
  if (data.siteName) setSiteName(data.siteName);
@@ -44,19 +45,21 @@ export function BrandingHub() {
44
45
  const handleSave = async () => {
45
46
  setSaving(true);
46
47
  try {
47
- await apiPatch("/api/globals/site", {
48
+ await apiPatch("/api/globals/site-settings", {
48
49
  siteName,
49
50
  adminTitle,
50
51
  primaryColor,
51
52
  dashboardGreeting,
52
53
  });
53
54
  setSaved(true);
54
- setTimeout(() => setSaved(false), 3000);
55
+ toast.success("Branding updated");
55
56
  document.documentElement.style.setProperty(
56
57
  "--kyro-primary",
57
58
  primaryColor,
58
59
  );
60
+ setTimeout(() => window.location.reload(), 800);
59
61
  } catch (e) {
62
+ toast.error("Failed to save branding");
60
63
  console.error(e);
61
64
  } finally {
62
65
  setSaving(false);
@@ -92,7 +95,7 @@ export function BrandingHub() {
92
95
  disabled={saving}
93
96
  className={`flex items-center gap-2 px-8 py-3 rounded-2xl font-bold text-sm shadow-xl transition-all active:scale-95 ${saved
94
97
  ? "bg-green-500 text-white"
95
- : "bg-[var(--kyro-primary)] text-white hover:shadow-[var(--kyro-primary)]"
98
+ : "kyro-btn-primary hover:shadow-[var(--kyro-primary)]"
96
99
  }`}
97
100
  >
98
101
  {saving ? (
@@ -5,6 +5,7 @@ import { AutoForm } from "./AutoForm";
5
5
  import { Spinner } from "./ui/Spinner";
6
6
  import { PageHeader } from "./ui/PageHeader";
7
7
  import { adminPath } from "../lib/paths";
8
+ import { toast } from "../lib/stores";
8
9
 
9
10
 
10
11
  interface CreateViewProps {
@@ -34,6 +35,7 @@ export function CreateView({
34
35
  try {
35
36
  setSaving(true);
36
37
  await apiPost(`/api/${collection.slug}`, data);
38
+ toast.success(`${collection.singularLabel || collection.label || "Document"} created`);
37
39
  onSuccess();
38
40
  } catch (err) {
39
41
  onError(err instanceof Error ? err.message : "Failed to create");
@@ -9,11 +9,12 @@ import { AutoForm } from "./AutoForm";
9
9
  import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
10
10
  import { Spinner } from "./ui/Spinner";
11
11
  import { Shimmer } from "./ui/Shimmer";
12
- import { useToast } from "./ui/Toast";
13
- import { useUIStore } from "../lib/stores";
12
+ import { useUIStore, toast } from "../lib/stores";
14
13
  import { PageHeader } from "./ui/PageHeader";
15
14
  import { Badge } from "./ui/Badge";
15
+ import { SplitButton } from "./ui/SplitButton";
16
16
  import { adminPath } from "../lib/paths";
17
+ import { resolveFieldValue } from "../lib/resolve-field-value";
17
18
 
18
19
 
19
20
  interface DetailViewProps {
@@ -39,7 +40,6 @@ export function DetailView({
39
40
  onError,
40
41
  mode = "collection",
41
42
  }: DetailViewProps) {
42
- const { addToast } = useToast();
43
43
  const { confirm, alert } = useUIStore();
44
44
  const [data, setData] = useState<Record<string, unknown>>({});
45
45
  const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
@@ -94,10 +94,10 @@ export function DetailView({
94
94
  const docData = result.data || {};
95
95
  setData(docData);
96
96
  setOriginalData(docData);
97
- setStatus((result.status || "draft") as DocumentStatus);
98
- setCreatedAt(result.createdAt || null);
99
- setUpdatedAt(result.updatedAt || null);
100
- setPublishedAt(result.publishedAt || null);
97
+ setStatus(((docData as any)?.status || result.status || "draft") as DocumentStatus);
98
+ setCreatedAt(result.createdAt || (docData.createdAt as string) || null);
99
+ setUpdatedAt(result.updatedAt || (docData.updatedAt as string) || null);
100
+ setPublishedAt(result.publishedAt || (docData.publishedAt as string) || null);
101
101
  } catch {
102
102
  onError("Failed to load document");
103
103
  } finally {
@@ -134,7 +134,8 @@ export function DetailView({
134
134
  ? `/api/globals/${slug}`
135
135
  : `/api/${slug}/${documentId}`;
136
136
 
137
- const result = (await apiPatch(endpoint, data) as { data?: Record<string, unknown> });
137
+ const isDraft = status === "draft" || (data as any)?.status === "draft";
138
+ const result = (await apiPatch(endpoint, data, { autoToast: false, headers: { "X-Draft": String(isDraft) } }) as { data?: Record<string, unknown> });
138
139
  const savedData = (result && (result.data || result)) || data;
139
140
 
140
141
  if (!isAutosave) {
@@ -143,6 +144,7 @@ export function DetailView({
143
144
  }
144
145
 
145
146
  setData(savedData);
147
+ setStatus((savedData as any)?.status || status);
146
148
  setSaveStatus("saved");
147
149
  setUpdatedAt(new Date().toISOString());
148
150
 
@@ -151,7 +153,9 @@ export function DetailView({
151
153
  setTimeout(() => setJustSaved(false), 3000);
152
154
 
153
155
  if (!isAutosave) {
154
- addToast?.("success", "Saved successfully");
156
+ const isDraft = status === "draft" || (savedData as any)?.status === "draft";
157
+ if (isDraft) toast.warning("Draft saved");
158
+ else toast.success("Updated");
155
159
  }
156
160
 
157
161
  setTimeout(() => {
@@ -161,25 +165,29 @@ export function DetailView({
161
165
  setSaveStatus("error");
162
166
  if (!isAutosave) {
163
167
  onError("Failed to save changes");
164
- addToast?.("error", "Failed to save changes");
168
+ toast.error("Failed to save changes");
165
169
  }
166
170
  } finally {
167
171
  setSaving(false);
168
172
  }
169
173
  },
170
- [data, mode, slug, documentId, onSave, onError],
174
+ [data, mode, slug, documentId, status, onSave, onError],
171
175
  );
172
176
 
173
177
  const handlePublish = async () => {
174
178
  try {
175
179
  setSaving(true);
176
- await apiPost(`/api/${slug}/${documentId}/publish`);
180
+ await apiPatch(`/api/${slug}/${documentId}`, data, {
181
+ autoToast: false,
182
+ headers: { "X-Draft": "false" },
183
+ } as any);
177
184
  setStatus("published");
178
185
  setPublishedAt(new Date().toISOString());
179
- addToast?.("success", "Published successfully");
186
+ toast.success("Published successfully");
187
+ onSave();
180
188
  } catch {
181
189
  onError("Failed to publish");
182
- addToast?.("error", "Failed to publish");
190
+ toast.error("Failed to publish");
183
191
  } finally {
184
192
  setSaving(false);
185
193
  }
@@ -188,10 +196,16 @@ export function DetailView({
188
196
  const handleUnpublish = async () => {
189
197
  try {
190
198
  setSaving(true);
191
- await apiPost(`/api/${slug}/${documentId}/unpublish`);
199
+ await apiPatch(`/api/${slug}/${documentId}`, { status: 'draft' }, {
200
+ autoToast: false,
201
+ headers: { "X-Draft": "false" },
202
+ } as any);
192
203
  setStatus("draft");
204
+ toast.warning("Document unpublished");
205
+ onSave();
193
206
  } catch {
194
207
  onError("Failed to unpublish");
208
+ toast.error("Failed to unpublish");
195
209
  } finally {
196
210
  setSaving(false);
197
211
  }
@@ -199,14 +213,15 @@ export function DetailView({
199
213
 
200
214
  const handleDuplicate = async () => {
201
215
  try {
202
- console.log(`[Duplicate] Calling /api/${slug}/${documentId}/duplicate`);
203
- const result = await apiPost(`/api/${slug}/${documentId}/duplicate`);
204
- console.log("[Duplicate] Success:", result);
205
- onError("Document duplicated successfully");
206
- } catch (err: unknown) {
207
- console.error("[Duplicate] Error:", err);
208
- const message = err instanceof Error ? err.message : "Failed to duplicate document";
209
- onError(message);
216
+ setSaving(true);
217
+ await apiPost(`/api/${slug}/${documentId}/duplicate`, undefined, { autoToast: false });
218
+ toast.success("Document duplicated");
219
+ } catch (err: unknown) {
220
+ const message = err instanceof Error ? err.message : "Failed to duplicate document";
221
+ onError(message);
222
+ toast.error(message);
223
+ } finally {
224
+ setSaving(false);
210
225
  }
211
226
  };
212
227
 
@@ -218,12 +233,12 @@ export function DetailView({
218
233
  onConfirm: async () => {
219
234
  try {
220
235
  setDeleting(true);
221
- await apiDelete(`/api/${slug}/${documentId}`);
236
+ await apiDelete(`/api/${slug}/${documentId}`, { autoToast: false });
222
237
  onDelete?.();
223
- } catch (err: unknown) {
224
- console.error("[Delete] Error:", err);
225
- const message = err instanceof Error ? err.message : "Failed to delete document";
226
- alert({ title: "Error", message });
238
+ toast.error("Document deleted");
239
+ } catch (err: unknown) {
240
+ const message = err instanceof Error ? err.message : "Failed to delete document";
241
+ toast.error(message);
227
242
  } finally {
228
243
  setDeleting(false);
229
244
  }
@@ -263,7 +278,7 @@ export function DetailView({
263
278
  { label: mode === "global" ? "Edit" : documentId ? "Edit" : "New" }
264
279
  ]}
265
280
  title={
266
- (mode === "global" ? label : (data[collection?.admin?.useAsTitle || "title"] as string || data.name as string || documentId || `New ${collection?.singularLabel || label}`))
281
+ (mode === "global" ? label : ((resolveFieldValue(collection?.fields as any, data, collection?.admin?.useAsTitle || "title") as string) || data.name as string || documentId || `New ${collection?.singularLabel || label}`))
267
282
  }
268
283
  metadata={[
269
284
  <Badge
@@ -325,8 +340,8 @@ export function DetailView({
325
340
  layout={isSingleLayout ? "single" : "split"}
326
341
  globalSlug={mode === "global" ? slug : undefined}
327
342
  collectionSlug={mode === "collection" ? slug : undefined}
328
- onActionSuccess={(message) => addToast?.("success", message)}
329
- onActionError={(message) => addToast?.("error", message)}
343
+ onActionSuccess={(message) => toast.success(message)}
344
+ onActionError={(message) => toast.error(message)}
330
345
  documentStatus={status}
331
346
  justSaved={justSaved}
332
347
  />
@@ -341,41 +356,13 @@ export function DetailView({
341
356
  Delete
342
357
  </button>
343
358
  )}
344
- <button
345
- type="button"
346
- onClick={() => handleSave(false)}
359
+ <SplitButton
360
+ status={status}
361
+ saveStatus={saving ? "saving" : "idle"}
362
+ hasChanges={hasChanges}
363
+ onPublish={() => handleSave(false)}
347
364
  disabled={saving}
348
- className="kyro-btn kyro-btn-lg kyro-btn-primary shadow-xl flex items-center gap-2"
349
- >
350
- {saving ? (
351
- <svg
352
- className="w-4 h-4 animate-spin"
353
- viewBox="0 0 24 24"
354
- fill="none"
355
- stroke="currentColor"
356
- strokeWidth="2"
357
- >
358
- <path d="M21 12a9 9 0 1 1-6.219-8.56" />
359
- </svg>
360
- ) : (
361
- <svg
362
- className="w-4 h-4"
363
- viewBox="0 0 24 24"
364
- fill="none"
365
- stroke="currentColor"
366
- strokeWidth="2"
367
- >
368
- <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
369
- <polyline points="17 21 17 13 7 13 7 21" />
370
- <polyline points="7 3 7 8 15 8" />
371
- </svg>
372
- )}
373
- {saving
374
- ? "Saving..."
375
- : mode === "global"
376
- ? "Save Configuration"
377
- : "Save Document"}
378
- </button>
365
+ />
379
366
  </div>
380
367
  )}
381
368
  </div>
@@ -17,7 +17,7 @@ import {
17
17
  import CodeMirror from "@uiw/react-codemirror";
18
18
  import { json } from "@codemirror/lang-json";
19
19
  import { aura } from "@uiw/codemirror-theme-aura";
20
- import { useUIStore } from "../lib/stores";
20
+ import { useUIStore, toast } from "../lib/stores";
21
21
  import { Modal, ModalContent, ModalActions } from "./ui/Modal";
22
22
  import { PageHeader } from "./ui/PageHeader";
23
23
  import { Badge } from "./ui/Badge";
@@ -70,9 +70,10 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
70
70
  loadKeys();
71
71
  setShowCreateModal(false);
72
72
  setNewKeyName("");
73
+ toast.success("API key generated");
73
74
  } catch (e) {
74
75
  console.error(e);
75
- alert({ title: "Error", message: "Failed to generate API key" });
76
+ toast.error("Failed to generate API key");
76
77
  }
77
78
  };
78
79
 
@@ -85,9 +86,10 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
85
86
  try {
86
87
  await apiDelete(`/api/keys/${id}`);
87
88
  loadKeys();
89
+ toast.success("API key revoked");
88
90
  } catch (e) {
89
91
  console.error(e);
90
- alert({ title: "Error", message: "Failed to revoke API key" });
92
+ toast.error("Failed to revoke API key");
91
93
  }
92
94
  }
93
95
  });
@@ -180,7 +182,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
180
182
  className="p-1.5 hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-all text-[var(--kyro-text-secondary)]"
181
183
  onClick={() => {
182
184
  navigator.clipboard.writeText(key.key);
183
- alert({ title: "Success", message: "API key copied to clipboard" });
185
+ toast.success("API key copied to clipboard");
184
186
  }}
185
187
  >
186
188
  <Copy className="w-4 h-4" />
@@ -311,7 +313,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
311
313
  type="button"
312
314
  onClick={handleRunTest}
313
315
  disabled={exploring || !testEndpoint}
314
- className="px-8 py-4 bg-[var(--kyro-primary)] text-white rounded-[1.5rem] font-bold text-sm shadow-xl disabled:opacity-50 disabled:cursor-not-allowed hover:scale-[1.02] transition-all flex items-center gap-3 shrink-0"
316
+ className="kyro-btn kyro-btn-primary px-8 py-4 rounded-[1.5rem] font-bold text-sm shadow-xl disabled:opacity-50 disabled:cursor-not-allowed hover:scale-[1.02] transition-all flex items-center gap-3 shrink-0"
315
317
  >
316
318
  {exploring ? (
317
319
  <RefreshCcw className="w-4 h-4 animate-spin" />
@@ -398,7 +400,7 @@ export function DeveloperCenter({ collections }: { collections: Record<string, u
398
400
  <button
399
401
  type="button"
400
402
  onClick={confirmGenerateKey}
401
- className="px-8 py-3 rounded-xl font-bold text-sm bg-[var(--kyro-primary)] text-white hover:opacity-90 shadow-lg shadow-[var(--kyro-primary)]/20 transition-all"
403
+ className="kyro-btn kyro-btn-primary px-8 py-3 rounded-xl font-bold text-sm hover:opacity-90 shadow-lg shadow-[var(--kyro-primary)]/20 transition-all"
402
404
  >
403
405
  Generate Token
404
406
  </button>
@@ -9,10 +9,15 @@ import DateField from "./fields/DateField";
9
9
  import { MarkdownField } from "./fields/MarkdownField";
10
10
  import TextField from "./fields/TextField";
11
11
  import { BlocksField } from "./fields/BlocksField";
12
- import PortableTextField from "./fields/PortableTextField";
12
+ import { RichTextField } from "./fields";
13
13
  import { ListField } from "./fields/ListField";
14
14
  import RelationshipField from "./fields/RelationshipField";
15
+ import SecretField from "./fields/SecretField";
15
16
  import FieldLayout from "./fields/FieldLayout";
17
+ import ArrayField from "./fields/ArrayField";
18
+ import { GroupLayout } from "./fields/GroupLayout";
19
+ import { ArrayLayout } from "./fields/ArrayLayout";
20
+ import { setChangeSource } from "../lib/change-source";
16
21
 
17
22
  interface FieldRendererProps {
18
23
  field: Field;
@@ -29,7 +34,12 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
29
34
  error,
30
35
  disabled,
31
36
  }) => {
32
- if (field.admin?.hidden) return null;
37
+ if (field.hidden === true || field.admin?.hidden === true) return null;
38
+
39
+ const onChangeKeystroke = (val: unknown) => {
40
+ setChangeSource("keystroke");
41
+ onChange(val);
42
+ };
33
43
 
34
44
  switch (field.type) {
35
45
  case "text":
@@ -39,7 +49,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
39
49
  <TextField
40
50
  field={field as any}
41
51
  value={value}
42
- onChange={onChange}
52
+ onChange={onChangeKeystroke}
43
53
  error={error}
44
54
  disabled={disabled}
45
55
  />
@@ -49,7 +59,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
49
59
  <TextField
50
60
  field={{ ...field, variant: "textarea" } as any}
51
61
  value={value}
52
- onChange={onChange}
62
+ onChange={onChangeKeystroke}
53
63
  error={error}
54
64
  disabled={disabled}
55
65
  />
@@ -59,7 +69,17 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
59
69
  <TextField
60
70
  field={{ ...field, variant: "password" } as any}
61
71
  value={value}
62
- onChange={onChange}
72
+ onChange={onChangeKeystroke}
73
+ error={error}
74
+ disabled={disabled}
75
+ />
76
+ );
77
+ case "secret":
78
+ return (
79
+ <SecretField
80
+ field={field as any}
81
+ value={value as string | null | undefined}
82
+ onChange={onChange as (value: string) => void}
63
83
  error={error}
64
84
  disabled={disabled}
65
85
  />
@@ -105,19 +125,11 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
105
125
  />
106
126
  );
107
127
  case "richtext":
108
- return (field as any).hasBlocks === false ? (
109
- <PortableTextField
110
- field={field as any}
111
- value={value}
112
- onChange={onChange}
113
- disabled={disabled}
114
- error={error}
115
- />
116
- ) : (
117
- <BlocksField
118
- field={field as any}
128
+ return (
129
+ <RichTextField
130
+ field={field}
119
131
  value={value}
120
- onChange={onChange}
132
+ onChange={onChangeKeystroke}
121
133
  disabled={disabled}
122
134
  error={error}
123
135
  />
@@ -127,7 +139,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
127
139
  <MarkdownField
128
140
  field={field as any}
129
141
  value={value}
130
- onChange={onChange}
142
+ onChange={onChangeKeystroke}
131
143
  disabled={disabled}
132
144
  error={error}
133
145
  />
@@ -137,7 +149,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
137
149
  <CodeField
138
150
  field={field as any}
139
151
  value={value}
140
- onChange={onChange}
152
+ onChange={onChangeKeystroke}
141
153
  disabled={disabled}
142
154
  error={error}
143
155
  />
@@ -174,6 +186,69 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
174
186
  />
175
187
  </FieldLayout>
176
188
  );
189
+ case "array":
190
+ return (
191
+ <ArrayLayout
192
+ field={field}
193
+ value={Array.isArray(value) ? value : []}
194
+ onChange={onChange}
195
+ disabled={disabled}
196
+ renderField={(nestedField, parentData, onNestedChange) => {
197
+ const nestedValue = parentData[nestedField.name];
198
+ return (
199
+ <FieldRenderer
200
+ key={nestedField.name}
201
+ field={nestedField}
202
+ value={nestedValue}
203
+ onChange={(val) => {
204
+ onNestedChange({
205
+ ...parentData,
206
+ [nestedField.name]: val,
207
+ });
208
+ }}
209
+ disabled={disabled}
210
+ error={error}
211
+ />
212
+ );
213
+ }}
214
+ />
215
+ );
216
+ case "blocks":
217
+ return (
218
+ <BlocksField
219
+ field={field as any}
220
+ value={value}
221
+ onChange={onChangeKeystroke}
222
+ disabled={disabled}
223
+ error={error}
224
+ />
225
+ );
226
+ case "group":
227
+ return (
228
+ <GroupLayout
229
+ field={field}
230
+ value={value as Record<string, unknown> | null}
231
+ onChange={onChange}
232
+ renderField={(nestedField, parentData, onNestedChange) => {
233
+ const nestedValue = parentData[nestedField.name];
234
+ return (
235
+ <FieldRenderer
236
+ key={nestedField.name}
237
+ field={nestedField}
238
+ value={nestedValue}
239
+ onChange={(val) => {
240
+ onNestedChange({
241
+ ...parentData,
242
+ [nestedField.name]: val,
243
+ });
244
+ }}
245
+ disabled={disabled}
246
+ error={error}
247
+ />
248
+ );
249
+ }}
250
+ />
251
+ );
177
252
  case "color":
178
253
  return (
179
254
  <FieldLayout field={field} error={error}>