@kyro-cms/admin 0.11.6 → 0.12.1
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 +25 -25
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +20 -20
- package/package.json +1 -1
- package/src/components/ActionBar.tsx +35 -5
- package/src/components/AutoForm.tsx +97 -40
- package/src/components/DetailView.tsx +41 -0
- package/src/components/FieldRenderer.tsx +11 -0
- package/src/components/GraphQLPlayground.tsx +259 -12
- package/src/components/MediaGallery.tsx +65 -51
- package/src/components/WebhookManager.tsx +338 -188
- package/src/components/autoform/AutoFormHeader.tsx +19 -9
- package/src/components/autoform/ErrorBoundary.tsx +93 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +2 -2
- package/src/components/blocks/HeroBlock.tsx +2 -2
- package/src/components/fields/BlocksField.tsx +69 -17
- package/src/components/fields/HeadingSubheadingField.tsx +2 -2
- package/src/components/fields/HeroField.tsx +4 -4
- package/src/components/fields/IconField.tsx +79 -0
- package/src/components/fields/RelationshipField.tsx +2 -2
- package/src/components/fields/UploadField.tsx +7 -5
- package/src/components/fields/extensions/blocksStore.ts +1 -1
- package/src/components/fields/index.ts +1 -0
- package/src/components/ui/BlockDrawer.tsx +39 -0
- package/src/components/ui/IconPickerModal.tsx +80 -0
- package/src/components/ui/ImageFocalEditor.tsx +112 -71
- package/src/components/ui/Modal.tsx +4 -2
- package/src/components/ui/icons.tsx +1 -1
- package/src/hooks/useAutoFormState.ts +51 -1
- package/src/integration.ts +4 -4
- package/src/pages/settings/[slug].astro +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
-
import { IconPlus, IconSend, IconClock, IconArchive, IconUndo, IconCopy, IconEye, IconTrash2, IconMoreVertical } from "./ui/icons";
|
|
2
|
+
import { IconPlus, IconSend, IconClock, IconArchive, IconUndo, IconCopy, IconEye, IconTrash2, IconMoreVertical, ClipboardPaste, ClipboardCopy } 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";
|
|
@@ -23,6 +23,8 @@ export interface ActionBarProps {
|
|
|
23
23
|
onToggleSidebar?: () => void;
|
|
24
24
|
publishedAt?: string | null;
|
|
25
25
|
updatedAt?: string | null;
|
|
26
|
+
onCopyData?: () => void;
|
|
27
|
+
onPasteData?: () => void;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const STATUS_DOT: Record<string, string> = {
|
|
@@ -54,6 +56,8 @@ export function ActionBar({
|
|
|
54
56
|
onToggleSidebar,
|
|
55
57
|
publishedAt,
|
|
56
58
|
updatedAt,
|
|
59
|
+
onCopyData,
|
|
60
|
+
onPasteData,
|
|
57
61
|
}: ActionBarProps) {
|
|
58
62
|
const view = useAutoFormStore((s) => s.view) || "edit";
|
|
59
63
|
const setView = useAutoFormStore((s) => s.setView);
|
|
@@ -130,6 +134,16 @@ export function ActionBar({
|
|
|
130
134
|
<IconCopy className="w-3.5 h-3.5" />
|
|
131
135
|
</button>
|
|
132
136
|
)}
|
|
137
|
+
{onCopyData && (
|
|
138
|
+
<button type="button" onClick={onCopyData} className={`${iconBtnClass} max-md:hidden`} title="Copy Data">
|
|
139
|
+
<ClipboardCopy className="w-3.5 h-3.5" />
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
{onPasteData && (
|
|
143
|
+
<button type="button" onClick={onPasteData} className={`${iconBtnClass} max-md:hidden`} title="Paste Data">
|
|
144
|
+
<ClipboardPaste className="w-3.5 h-3.5" />
|
|
145
|
+
</button>
|
|
146
|
+
)}
|
|
133
147
|
{onDelete && (
|
|
134
148
|
<button type="button" onClick={onDelete} className={`${iconBtnClass} hover:bg-[var(--kyro-danger-bg)] hover:text-[var(--kyro-danger)] max-md:hidden`} title="Delete">
|
|
135
149
|
<IconTrash2 className="w-3.5 h-3.5" />
|
|
@@ -183,6 +197,16 @@ export function ActionBar({
|
|
|
183
197
|
<IconCopy className="w-3.5 h-3.5" /> Duplicate
|
|
184
198
|
</button>
|
|
185
199
|
)}
|
|
200
|
+
{onCopyData && (
|
|
201
|
+
<button type="button" onClick={() => { onCopyData(); 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">
|
|
202
|
+
<ClipboardCopy className="w-3.5 h-3.5" /> Copy Data
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
205
|
+
{onPasteData && (
|
|
206
|
+
<button type="button" onClick={() => { onPasteData(); 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">
|
|
207
|
+
<ClipboardPaste className="w-3.5 h-3.5" /> Paste Data
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
186
210
|
{onPreview && (
|
|
187
211
|
<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
212
|
<IconEye className="w-3.5 h-3.5" /> Preview
|
|
@@ -225,15 +249,21 @@ export function ActionBar({
|
|
|
225
249
|
onPublish={onSave}
|
|
226
250
|
>
|
|
227
251
|
{onDuplicate && (
|
|
228
|
-
<DropdownItem icon={<IconCopy className="w-4 h-4" />}>Duplicate</DropdownItem>
|
|
252
|
+
<DropdownItem icon={<IconCopy className="w-4 h-4" />} onClick={onDuplicate}>Duplicate</DropdownItem>
|
|
253
|
+
)}
|
|
254
|
+
{onCopyData && (
|
|
255
|
+
<DropdownItem icon={<ClipboardCopy className="w-4 h-4" />} onClick={onCopyData}>Copy Data</DropdownItem>
|
|
256
|
+
)}
|
|
257
|
+
{onPasteData && (
|
|
258
|
+
<DropdownItem icon={<ClipboardPaste className="w-4 h-4" />} onClick={onPasteData}>Paste Data</DropdownItem>
|
|
229
259
|
)}
|
|
230
260
|
{onViewHistory && (
|
|
231
|
-
<DropdownItem icon={<IconClock className="w-4 h-4" />}>View History</DropdownItem>
|
|
261
|
+
<DropdownItem icon={<IconClock className="w-4 h-4" />} onClick={onViewHistory}>View History</DropdownItem>
|
|
232
262
|
)}
|
|
233
263
|
{onPreview && (
|
|
234
|
-
<DropdownItem icon={<IconEye className="w-4 h-4" />}>Preview</DropdownItem>
|
|
264
|
+
<DropdownItem icon={<IconEye className="w-4 h-4" />} onClick={onPreview}>Preview</DropdownItem>
|
|
235
265
|
)}
|
|
236
|
-
{(onDuplicate || onViewHistory || onPreview) && <DropdownSeparator />}
|
|
266
|
+
{(onDuplicate || onCopyData || onPasteData || onViewHistory || onPreview) && <DropdownSeparator />}
|
|
237
267
|
{onDelete && (
|
|
238
268
|
<DropdownItem onClick={onDelete} danger icon={<IconTrash2 className="w-4 h-4" />}>Delete</DropdownItem>
|
|
239
269
|
)}
|
|
@@ -43,6 +43,7 @@ import { AutoFormHeader } from "./autoform/AutoFormHeader";
|
|
|
43
43
|
import { AutoFormEditView } from "./autoform/AutoFormEditView";
|
|
44
44
|
import { AutoFormVersionView } from "./autoform/AutoFormVersionView";
|
|
45
45
|
import { AutoFormApiView } from "./autoform/AutoFormApiView";
|
|
46
|
+
import { ErrorBoundary } from "./autoform/ErrorBoundary";
|
|
46
47
|
|
|
47
48
|
interface AutoFormProps {
|
|
48
49
|
config: CollectionConfig | GlobalConfig;
|
|
@@ -61,8 +62,49 @@ interface AutoFormProps {
|
|
|
61
62
|
documentStatus?: string;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
function getNestedValue(obj: any, path: string): any {
|
|
66
|
+
if (!obj || typeof path !== "string") return undefined;
|
|
67
|
+
return path.split('.').reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function evaluateDeclarativeCondition(cond: any, currentData: any, formData: any): boolean {
|
|
71
|
+
if (!cond) return true;
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(cond.and)) {
|
|
74
|
+
return cond.and.every((c: any) => evaluateDeclarativeCondition(c, currentData, formData));
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(cond.or)) {
|
|
77
|
+
return cond.or.some((c: any) => evaluateDeclarativeCondition(c, currentData, formData));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (cond.field) {
|
|
81
|
+
const targetField = cond.field;
|
|
82
|
+
let val = getNestedValue(currentData, targetField);
|
|
83
|
+
if (val === undefined) {
|
|
84
|
+
val = getNestedValue(formData, targetField);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ("equals" in cond) {
|
|
88
|
+
return val === cond.equals;
|
|
89
|
+
}
|
|
90
|
+
if ("notEquals" in cond) {
|
|
91
|
+
return val !== cond.notEquals;
|
|
92
|
+
}
|
|
93
|
+
if ("in" in cond && Array.isArray(cond.in)) {
|
|
94
|
+
return cond.in.includes(val);
|
|
95
|
+
}
|
|
96
|
+
if ("greaterThan" in cond) {
|
|
97
|
+
return typeof val === "number" && val > cond.greaterThan;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If field exists but no operator is provided, just check truthiness
|
|
101
|
+
return Boolean(val);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
65
106
|
|
|
107
|
+
const EMPTY_OBJECT = {};
|
|
66
108
|
export function AutoForm({
|
|
67
109
|
config: propConfig,
|
|
68
110
|
data: initialData = EMPTY_OBJECT,
|
|
@@ -147,6 +189,7 @@ export function AutoForm({
|
|
|
147
189
|
setAutoSaveStatus,
|
|
148
190
|
fetchVersions,
|
|
149
191
|
saveDocument,
|
|
192
|
+
forceSave,
|
|
150
193
|
autoSaveSkipRef,
|
|
151
194
|
lastAutoSaveTimeRef,
|
|
152
195
|
documentStatus,
|
|
@@ -460,11 +503,14 @@ export function AutoForm({
|
|
|
460
503
|
message: "Delete this document? This cannot be undone. Are you absolutely sure?",
|
|
461
504
|
variant: "danger",
|
|
462
505
|
onConfirm: async () => {
|
|
506
|
+
autoSaveSkipRef.current = true;
|
|
463
507
|
try {
|
|
464
508
|
await apiDelete(`/api/${collectionSlug}/${formData.id}`);
|
|
465
509
|
window.location.href = `${ADMIN_BASE}/${collectionSlug}`;
|
|
466
510
|
} catch (err) {
|
|
467
511
|
toast.error((err as Error).message || "Failed to delete document");
|
|
512
|
+
} finally {
|
|
513
|
+
autoSaveSkipRef.current = false;
|
|
468
514
|
}
|
|
469
515
|
},
|
|
470
516
|
});
|
|
@@ -475,6 +521,7 @@ export function AutoForm({
|
|
|
475
521
|
title: "Unpublish Document",
|
|
476
522
|
message: "Unpublish this document?",
|
|
477
523
|
onConfirm: async () => {
|
|
524
|
+
autoSaveSkipRef.current = true;
|
|
478
525
|
try {
|
|
479
526
|
const response = await saveDocument(
|
|
480
527
|
{ ...formData, status: 'draft' } as Record<string, unknown>,
|
|
@@ -493,6 +540,8 @@ export function AutoForm({
|
|
|
493
540
|
}
|
|
494
541
|
} catch (err) {
|
|
495
542
|
toast.error("Failed to unpublish");
|
|
543
|
+
} finally {
|
|
544
|
+
autoSaveSkipRef.current = false;
|
|
496
545
|
}
|
|
497
546
|
},
|
|
498
547
|
});
|
|
@@ -533,7 +582,7 @@ export function AutoForm({
|
|
|
533
582
|
);
|
|
534
583
|
if (isPost) {
|
|
535
584
|
setTimeout(() => {
|
|
536
|
-
window.location.href = `${ADMIN_BASE}/${collectionSlug}`;
|
|
585
|
+
window.location.href = `${ADMIN_BASE}/${collectionSlug}/${result.data.id}`;
|
|
537
586
|
}, 800);
|
|
538
587
|
}
|
|
539
588
|
} else {
|
|
@@ -593,7 +642,22 @@ export function AutoForm({
|
|
|
593
642
|
if (response?.ok) {
|
|
594
643
|
setLocalSaveStatus("saved");
|
|
595
644
|
onActionSuccess?.("Published successfully");
|
|
645
|
+
if (isNewDoc && !globalSlug && dataToPublish.id) {
|
|
646
|
+
setTimeout(() => {
|
|
647
|
+
window.location.href = `${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`;
|
|
648
|
+
}, 800);
|
|
649
|
+
}
|
|
596
650
|
} else {
|
|
651
|
+
// Two-step publish: if POST succeeded but PATCH failed,
|
|
652
|
+
// document exists as draft — navigate and inform user
|
|
653
|
+
if (isNewDoc && !globalSlug && dataToPublish.id) {
|
|
654
|
+
const error = await response?.json().catch(() => ({}));
|
|
655
|
+
toast.warning("Document saved as draft. Publishing failed: " + (error?.error || "Unknown error"));
|
|
656
|
+
setTimeout(() => {
|
|
657
|
+
window.location.href = `${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`;
|
|
658
|
+
}, 1200);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
597
661
|
const error = await response?.json().catch(() => ({}));
|
|
598
662
|
if (response?.status === 409) setAutoSaveStatus("conflict");
|
|
599
663
|
setLocalSaveStatus("error");
|
|
@@ -683,23 +747,7 @@ export function AutoForm({
|
|
|
683
747
|
}
|
|
684
748
|
} else if (typeof field.admin.condition === "object") {
|
|
685
749
|
try {
|
|
686
|
-
const
|
|
687
|
-
const targetField = cond.field;
|
|
688
|
-
|
|
689
|
-
// Get target field value, prioritizing sibling context (currentData) then root context (formData)
|
|
690
|
-
const val = (currentData && currentData[targetField] !== undefined)
|
|
691
|
-
? currentData[targetField]
|
|
692
|
-
: (formData && formData[targetField] !== undefined ? formData[targetField] : undefined);
|
|
693
|
-
|
|
694
|
-
let shouldShow = true;
|
|
695
|
-
if ("equals" in cond) {
|
|
696
|
-
shouldShow = val === cond.equals;
|
|
697
|
-
} else if ("notEquals" in cond) {
|
|
698
|
-
shouldShow = val !== cond.notEquals;
|
|
699
|
-
} else if ("in" in cond && Array.isArray(cond.in)) {
|
|
700
|
-
shouldShow = cond.in.includes(val);
|
|
701
|
-
}
|
|
702
|
-
|
|
750
|
+
const shouldShow = evaluateDeclarativeCondition(field.admin.condition, currentData, formData);
|
|
703
751
|
if (!shouldShow) {
|
|
704
752
|
return null;
|
|
705
753
|
}
|
|
@@ -1012,7 +1060,9 @@ export function AutoForm({
|
|
|
1012
1060
|
handleUnpublish={handleUnpublish}
|
|
1013
1061
|
handleDelete={handleDelete}
|
|
1014
1062
|
handlePublish={handlePublish}
|
|
1063
|
+
handleSaveDraft={handleSaveDraft}
|
|
1015
1064
|
handleSchedulePublish={handleSchedulePublish}
|
|
1065
|
+
handleConflictOverride={() => forceSave()}
|
|
1016
1066
|
/>
|
|
1017
1067
|
)}
|
|
1018
1068
|
{layout === "single" && (
|
|
@@ -1068,7 +1118,9 @@ export function AutoForm({
|
|
|
1068
1118
|
type="button"
|
|
1069
1119
|
style={{ width: 0, height: 0, opacity: 0, padding: 0, margin: 0, border: 'none', position: 'absolute' }}
|
|
1070
1120
|
onClick={async () => {
|
|
1121
|
+
autoSaveSkipRef.current = true;
|
|
1071
1122
|
try {
|
|
1123
|
+
window.dispatchEvent(new Event("kyro:global-save-start"));
|
|
1072
1124
|
const response = await saveDocument(formData);
|
|
1073
1125
|
if (response.ok) {
|
|
1074
1126
|
const result = await response.json();
|
|
@@ -1080,33 +1132,38 @@ export function AutoForm({
|
|
|
1080
1132
|
} catch (e) {
|
|
1081
1133
|
console.error("Save error exception:", e);
|
|
1082
1134
|
onActionError?.("Save failed: " + (e as Error).message);
|
|
1135
|
+
} finally {
|
|
1136
|
+
autoSaveSkipRef.current = false;
|
|
1137
|
+
window.dispatchEvent(new Event("kyro:global-save-end"));
|
|
1083
1138
|
}
|
|
1084
1139
|
}}
|
|
1085
1140
|
/>
|
|
1086
1141
|
</>
|
|
1087
1142
|
)}
|
|
1088
1143
|
<main className="w-full pt-6 md:pt-0">
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1144
|
+
<ErrorBoundary>
|
|
1145
|
+
{view === "edit" && (
|
|
1146
|
+
<AutoFormEditView
|
|
1147
|
+
config={config}
|
|
1148
|
+
layout={layout}
|
|
1149
|
+
collectionSlug={collectionSlug}
|
|
1150
|
+
renderField={renderField}
|
|
1151
|
+
/>
|
|
1152
|
+
)}
|
|
1153
|
+
{view === "version" && (
|
|
1154
|
+
<AutoFormVersionView
|
|
1155
|
+
handleRestoreVersion={handleRestoreVersion}
|
|
1156
|
+
handleCompareVersions={handleCompareVersions}
|
|
1157
|
+
toggleCompareSelection={toggleCompareSelection}
|
|
1158
|
+
/>
|
|
1159
|
+
)}
|
|
1160
|
+
{view === "api" && (
|
|
1161
|
+
<AutoFormApiView
|
|
1162
|
+
collectionSlug={collectionSlug}
|
|
1163
|
+
globalSlug={globalSlug}
|
|
1164
|
+
/>
|
|
1165
|
+
)}
|
|
1166
|
+
</ErrorBoundary>
|
|
1110
1167
|
</main>
|
|
1111
1168
|
</div>
|
|
1112
1169
|
);
|
|
@@ -229,6 +229,45 @@ export function DetailView({
|
|
|
229
229
|
}
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
+
const handleCopyData = async () => {
|
|
233
|
+
try {
|
|
234
|
+
const copyPayload = { ...data };
|
|
235
|
+
// Strip system fields
|
|
236
|
+
delete copyPayload.id;
|
|
237
|
+
delete copyPayload.createdAt;
|
|
238
|
+
delete copyPayload.updatedAt;
|
|
239
|
+
delete (copyPayload as any).status;
|
|
240
|
+
|
|
241
|
+
await navigator.clipboard.writeText(JSON.stringify(copyPayload));
|
|
242
|
+
toast.success("Document data copied to clipboard");
|
|
243
|
+
} catch (e) {
|
|
244
|
+
toast.error("Failed to copy document data");
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const handlePasteData = async () => {
|
|
249
|
+
try {
|
|
250
|
+
const text = await navigator.clipboard.readText();
|
|
251
|
+
const pastedData = JSON.parse(text);
|
|
252
|
+
if (typeof pastedData !== 'object' || pastedData === null) {
|
|
253
|
+
throw new Error("Invalid format");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
setData((prev) => ({
|
|
257
|
+
...prev,
|
|
258
|
+
...pastedData,
|
|
259
|
+
// Preserve system fields from overwrite
|
|
260
|
+
id: prev.id,
|
|
261
|
+
createdAt: prev.createdAt,
|
|
262
|
+
updatedAt: prev.updatedAt,
|
|
263
|
+
status: prev.status,
|
|
264
|
+
}));
|
|
265
|
+
toast.success("Document data pasted");
|
|
266
|
+
} catch (e) {
|
|
267
|
+
toast.error("Clipboard does not contain valid document JSON");
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
232
271
|
const handleDeleteTrigger = () => {
|
|
233
272
|
confirm({
|
|
234
273
|
title: `Delete ${label}?`,
|
|
@@ -300,6 +339,8 @@ export function DetailView({
|
|
|
300
339
|
onPublish={handlePublish}
|
|
301
340
|
onUnpublish={status === "published" ? handleUnpublish : undefined}
|
|
302
341
|
onDuplicate={handleDuplicate}
|
|
342
|
+
onCopyData={handleCopyData}
|
|
343
|
+
onPasteData={handlePasteData}
|
|
303
344
|
onViewHistory={() => {
|
|
304
345
|
window.dispatchEvent(new CustomEvent('kyro:show-version-history'));
|
|
305
346
|
}}
|
|
@@ -8,6 +8,7 @@ import SelectField from "./fields/SelectField";
|
|
|
8
8
|
import DateField from "./fields/DateField";
|
|
9
9
|
import { MarkdownField } from "./fields/MarkdownField";
|
|
10
10
|
import TextField from "./fields/TextField";
|
|
11
|
+
import IconField from "./fields/IconField";
|
|
11
12
|
import { BlocksField } from "./fields/BlocksField";
|
|
12
13
|
import { RichTextField } from "./fields";
|
|
13
14
|
import { ListField } from "./fields/ListField";
|
|
@@ -62,6 +63,16 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|
|
62
63
|
disabled={disabled}
|
|
63
64
|
/>
|
|
64
65
|
);
|
|
66
|
+
case "icon":
|
|
67
|
+
return (
|
|
68
|
+
<IconField
|
|
69
|
+
field={field as any}
|
|
70
|
+
value={value}
|
|
71
|
+
onChange={onChangeKeystroke}
|
|
72
|
+
error={error}
|
|
73
|
+
disabled={disabled}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
65
76
|
case "textarea":
|
|
66
77
|
return (
|
|
67
78
|
<TextField
|