@kyro-cms/admin 0.8.0 → 0.9.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 +11960 -11006
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +67 -65
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +563 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +12183 -11238
- package/dist/index.js.map +1 -1
- package/package.json +15 -11
- package/src/components/ActionBar.tsx +27 -14
- package/src/components/Admin.tsx +1 -1
- package/src/components/ApiKeysManager.tsx +5 -5
- package/src/components/AutoForm.tsx +585 -369
- package/src/components/BrandingHub.tsx +7 -4
- package/src/components/CreateView.tsx +2 -0
- package/src/components/DetailView.tsx +71 -56
- package/src/components/DeveloperCenter.tsx +8 -6
- package/src/components/FieldRenderer.tsx +94 -19
- package/src/components/ListView.tsx +33 -20
- package/src/components/MediaGallery.tsx +219 -194
- package/src/components/PluginsManager.tsx +197 -70
- package/src/components/RestPlayground.tsx +7 -7
- package/src/components/SessionsManager.tsx +1 -1
- package/src/components/SettingsPage.tsx +22 -0
- package/src/components/Sidebar.astro +13 -41
- package/src/components/UserManagement.tsx +153 -15
- package/src/components/UserMenu.tsx +30 -4
- package/src/components/VersionHistoryPanel.tsx +112 -119
- package/src/components/WebhookManager.tsx +6 -4
- package/src/components/blocks/ArrayBlock.tsx +6 -23
- package/src/components/blocks/BlockEditModal.tsx +82 -309
- package/src/components/blocks/CardBlock.tsx +35 -0
- package/src/components/blocks/ChildBlocksTree.tsx +57 -31
- package/src/components/blocks/GenericBlock.tsx +44 -0
- package/src/components/blocks/HeadingSubheadingBlock.tsx +32 -0
- package/src/components/blocks/HeroBlock.tsx +5 -14
- package/src/components/blocks/RichTextBlock.tsx +5 -5
- package/src/components/blocks/index.ts +5 -3
- package/src/components/fields/AccordionField.tsx +2 -2
- package/src/components/fields/ArrayField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +120 -29
- package/src/components/fields/BlocksField.tsx +430 -50
- package/src/components/fields/CardField.tsx +73 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/DateField.tsx +4 -1
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/HeadingSubheadingField.tsx +43 -0
- package/src/components/fields/ListField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -1
- package/src/components/fields/RelationshipField.tsx +153 -87
- package/src/components/fields/RichTextField.tsx +781 -0
- package/src/components/fields/SecretField.tsx +102 -0
- package/src/components/fields/SelectField.tsx +19 -6
- package/src/components/fields/TabsLayout.tsx +19 -9
- package/src/components/fields/TextField.tsx +4 -1
- package/src/components/fields/UploadField.tsx +122 -56
- package/src/components/fields/extensions/blockComponents.tsx +103 -174
- package/src/components/fields/extensions/blocksStore.ts +8 -1
- package/src/components/fields/index.ts +4 -2
- package/src/components/ui/PageHeader.tsx +5 -5
- package/src/components/ui/SlidePanel.tsx +8 -3
- package/src/components/ui/icons.tsx +109 -109
- package/src/components/users/UserDetail.tsx +79 -16
- package/src/hooks/useAutoFormState.ts +125 -62
- package/src/integration.ts +148 -46
- package/src/kyro-cms.d.ts +7 -2
- package/src/layouts/AuthLayout.astro +14 -2
- package/src/lib/autoform-store.ts +85 -52
- package/src/lib/change-source.ts +9 -0
- package/src/lib/config.ts +104 -8
- package/src/lib/globals.ts +44 -9
- package/src/lib/normalize-upload-fields.ts +41 -0
- package/src/lib/paths.ts +2 -2
- package/src/lib/resolve-field-value.ts +110 -0
- package/src/lib/shim/use-sync-external-store-with-selector.js +30 -0
- package/src/lib/shim/use-sync-external-store.js +1 -0
- package/src/lib/stores/index.ts +1 -0
- package/src/lib/useResourceManager.ts +4 -4
- package/src/lib/vite-shim-plugin.ts +100 -0
- package/src/pages/[collection]/[id].astro +1 -1
- package/src/pages/preview/[collection]/[id].astro +4 -4
- package/src/pages/settings/[slug].astro +2 -2
- package/src/styles/main.css +60 -54
- package/README.md +0 -46
- package/dist/EditorClient-Q23UXR37.cjs +0 -468
- package/dist/EditorClient-Q23UXR37.cjs.map +0 -1
- package/dist/EditorClient-T5PASFNR.js +0 -466
- package/dist/EditorClient-T5PASFNR.js.map +0 -1
- package/dist/chunk-3BGDYKTD.cjs +0 -348
- package/dist/chunk-3BGDYKTD.cjs.map +0 -1
- package/dist/chunk-EEFXLQVT.js +0 -3
- package/dist/chunk-EEFXLQVT.js.map +0 -1
- package/src/components/blocks/ButtonBlock.tsx +0 -64
- package/src/components/blocks/ColumnsBlock.tsx +0 -55
- package/src/components/blocks/DividerBlock.tsx +0 -43
- package/src/components/blocks/LinkBlock.tsx +0 -65
- package/src/components/blocks/VStackBlock.tsx +0 -29
- package/src/components/fields/EditorClient.tsx +0 -535
- package/src/components/fields/PortableTextField.tsx +0 -155
- package/src/components/fields/PortableTextRenderer.tsx +0 -68
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "../ui/icons";
|
|
7
|
-
import { ArrayField } from "../fields/ArrayField";
|
|
8
7
|
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
9
8
|
|
|
10
9
|
export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
@@ -16,7 +15,6 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
16
15
|
|
|
17
16
|
const data = blockData?.data ?? block.data ?? {};
|
|
18
17
|
const children = blockData?.children ?? block.children ?? [];
|
|
19
|
-
const items = Array.isArray(data.items) ? data.items : [];
|
|
20
18
|
|
|
21
19
|
return (
|
|
22
20
|
<div className="block-array border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
@@ -26,7 +24,7 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
26
24
|
Repeater
|
|
27
25
|
</span>
|
|
28
26
|
<span className="text-[10px] text-[var(--kyro-text-muted)]">
|
|
29
|
-
({
|
|
27
|
+
({children.length} items)
|
|
30
28
|
</span>
|
|
31
29
|
</div>
|
|
32
30
|
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
@@ -58,26 +56,11 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
|
|
|
58
56
|
</div>
|
|
59
57
|
|
|
60
58
|
<div className="space-y-3">
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
compact
|
|
68
|
-
/>
|
|
69
|
-
)}
|
|
70
|
-
|
|
71
|
-
<div className="pt-2 border-t border-[var(--kyro-border)]">
|
|
72
|
-
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
73
|
-
Children ({children.length})
|
|
74
|
-
</label>
|
|
75
|
-
<ChildBlocksTree
|
|
76
|
-
blockId={block.id}
|
|
77
|
-
children={children}
|
|
78
|
-
onUpdateChildren={(c) => updateBlock(block.id, { children: c })}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
59
|
+
<ChildBlocksTree
|
|
60
|
+
blockId={block.id}
|
|
61
|
+
children={children}
|
|
62
|
+
onUpdateChildren={(c) => updateBlock(block.id, { children: c })}
|
|
63
|
+
/>
|
|
81
64
|
</div>
|
|
82
65
|
</div>
|
|
83
66
|
);
|
|
@@ -1,38 +1,22 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { ChevronRight } from "../ui/icons";
|
|
3
2
|
import {
|
|
4
3
|
useBlockById,
|
|
5
4
|
useBlockActions,
|
|
6
5
|
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { blockTheme } from "../fields/extensions/blockComponents";
|
|
7
7
|
import { SlidePanel } from "../ui/SlidePanel";
|
|
8
8
|
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
9
|
-
import {
|
|
10
|
-
import PortableTextField from "../fields/PortableTextField";
|
|
11
|
-
import {
|
|
12
|
-
CodeField,
|
|
13
|
-
LinkField,
|
|
14
|
-
AccordionField,
|
|
15
|
-
ButtonField,
|
|
16
|
-
HeadingField,
|
|
17
|
-
VideoField,
|
|
18
|
-
ListField,
|
|
19
|
-
HeroField,
|
|
20
|
-
ArrayField,
|
|
21
|
-
ChildrenField,
|
|
22
|
-
ColumnsField,
|
|
23
|
-
RelationshipBlockField,
|
|
24
|
-
} from "../fields";
|
|
9
|
+
import { FieldRenderer } from "../FieldRenderer";
|
|
25
10
|
|
|
26
11
|
interface BlockEditModalProps {
|
|
27
12
|
block: Record<string, unknown>;
|
|
13
|
+
blockSchema?: Record<string, any>;
|
|
28
14
|
onClose: () => void;
|
|
29
15
|
}
|
|
30
16
|
|
|
31
|
-
// @MARKER: BlockEditModal with children - 2026-04-29
|
|
32
|
-
// If you want to revert, check git history for previous version
|
|
33
|
-
|
|
34
17
|
export const BlockEditModal: React.FC<BlockEditModalProps> = ({
|
|
35
18
|
block,
|
|
19
|
+
blockSchema,
|
|
36
20
|
onClose,
|
|
37
21
|
}) => {
|
|
38
22
|
const blockData = useBlockById(block.id);
|
|
@@ -48,307 +32,96 @@ export const BlockEditModal: React.FC<BlockEditModalProps> = ({
|
|
|
48
32
|
updateBlock(block.id, { children: newChildren });
|
|
49
33
|
};
|
|
50
34
|
|
|
51
|
-
const handleUpdateColumnChildren = (
|
|
52
|
-
columnIndex: number,
|
|
53
|
-
newChildren: Record<string, unknown>[],
|
|
54
|
-
) => {
|
|
55
|
-
const columnData = data.columnData || [];
|
|
56
|
-
const newColumnData = [...columnData];
|
|
57
|
-
newColumnData[columnIndex] = {
|
|
58
|
-
...newColumnData[columnIndex],
|
|
59
|
-
children: newChildren,
|
|
60
|
-
};
|
|
61
|
-
updateBlock(block.id, { data: { ...data, columnData: newColumnData } });
|
|
62
|
-
};
|
|
63
|
-
|
|
64
35
|
const renderFields = () => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div className="space-y-4">
|
|
101
|
-
<UploadField
|
|
102
|
-
field={{ label: "Image", name: "image", maxCount: 1 }}
|
|
103
|
-
value={data.src}
|
|
104
|
-
onChange={(value) => handleChange("src", value)}
|
|
105
|
-
/>
|
|
106
|
-
<div>
|
|
107
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
108
|
-
Alt Text
|
|
109
|
-
</label>
|
|
110
|
-
<input
|
|
111
|
-
type="text"
|
|
112
|
-
value={data.alt || ""}
|
|
113
|
-
onChange={(e) => handleChange("alt", e.target.value)}
|
|
114
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-sm text-[var(--kyro-text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent"
|
|
115
|
-
placeholder="Description for accessibility..."
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
case "video":
|
|
122
|
-
return (
|
|
123
|
-
<VideoField
|
|
124
|
-
src={data.src || ""}
|
|
125
|
-
title={data.title || ""}
|
|
126
|
-
onChange={handleChange}
|
|
127
|
-
compact
|
|
128
|
-
/>
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
case "link":
|
|
132
|
-
return (
|
|
133
|
-
<div className="space-y-3">
|
|
134
|
-
<LinkField
|
|
135
|
-
text={data.text || ""}
|
|
136
|
-
url={data.url || ""}
|
|
137
|
-
onChange={handleChange}
|
|
138
|
-
compact
|
|
139
|
-
/>
|
|
140
|
-
</div>
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
case "button":
|
|
144
|
-
return (
|
|
145
|
-
<ButtonField
|
|
146
|
-
text={data.text || "Button"}
|
|
147
|
-
url={data.url || ""}
|
|
148
|
-
onChange={handleChange}
|
|
149
|
-
compact
|
|
150
|
-
/>
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
case "list":
|
|
154
|
-
return (
|
|
155
|
-
<ListField
|
|
156
|
-
items={Array.isArray(data.items) ? data.items : []}
|
|
157
|
-
onChange={(items) => handleChange("items", items)}
|
|
158
|
-
compact
|
|
159
|
-
/>
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
case "code":
|
|
163
|
-
const languages = [
|
|
164
|
-
{ value: "plaintext", label: "Text", icon: "📄" },
|
|
165
|
-
{ value: "javascript", label: "JS", icon: "🟨" },
|
|
166
|
-
{ value: "typescript", label: "TS", icon: "🔷" },
|
|
167
|
-
{ value: "python", label: "PY", icon: "🐍" },
|
|
168
|
-
{ value: "html", label: "HTML", icon: "🌐" },
|
|
169
|
-
{ value: "css", label: "CSS", icon: "🎨" },
|
|
170
|
-
{ value: "json", label: "JSON", icon: "📋" },
|
|
171
|
-
];
|
|
172
|
-
return (
|
|
173
|
-
<div className="space-y-4">
|
|
174
|
-
<CodeField
|
|
175
|
-
field={{
|
|
176
|
-
type: "code",
|
|
177
|
-
name: "code",
|
|
178
|
-
label: "Snippet",
|
|
179
|
-
language: data.language || "javascript",
|
|
180
|
-
}}
|
|
181
|
-
value={data.code || ""}
|
|
182
|
-
onChange={(val) => handleChange("code", val)}
|
|
183
|
-
/>
|
|
184
|
-
|
|
185
|
-
<div className="grid grid-cols-2 gap-3">
|
|
186
|
-
<div>
|
|
187
|
-
<label className="text-[10px] font-bold tracking-widest text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
188
|
-
Language
|
|
189
|
-
</label>
|
|
190
|
-
<div className="relative">
|
|
191
|
-
<select
|
|
192
|
-
value={data.language || "javascript"}
|
|
193
|
-
onChange={(e) => handleChange("language", e.target.value)}
|
|
194
|
-
className="w-full pl-3 pr-10 py-2.5 bg-[var(--kyro-bg-secondary)] border border-[var(--kyro-border)] rounded-xl text-xs font-medium text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)]/20 transition-all appearance-none cursor-pointer"
|
|
195
|
-
>
|
|
196
|
-
<option value="plaintext">Plain Text</option>
|
|
197
|
-
<option value="javascript">JS</option>
|
|
198
|
-
<option value="typescript">TS</option>
|
|
199
|
-
<option value="python">PY</option>
|
|
200
|
-
<option value="html">HTML</option>
|
|
201
|
-
<option value="css">CSS</option>
|
|
202
|
-
<option value="json">JSON</option>
|
|
203
|
-
<option value="rust">Rust</option>
|
|
204
|
-
</select>
|
|
205
|
-
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-[var(--kyro-text-muted)]">
|
|
206
|
-
<ChevronRight className="w-4 h-4 rotate-90" />
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
</div>
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
case "file":
|
|
215
|
-
return (
|
|
216
|
-
<UploadField
|
|
217
|
-
field={{ label: "File", name: "file", maxCount: 1 }}
|
|
218
|
-
value={data.file}
|
|
219
|
-
onChange={(value) => handleChange("file", value)}
|
|
220
|
-
/>
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
case "relationship":
|
|
224
|
-
return (
|
|
225
|
-
<RelationshipBlockField
|
|
226
|
-
relationTo={data.relationTo || "pages"}
|
|
227
|
-
hasMany={data.hasMany || false}
|
|
228
|
-
selectedIds={
|
|
229
|
-
Array.isArray(data.selectedIds) ? data.selectedIds : []
|
|
36
|
+
// If a schema is present, render all fields dynamically using FieldRenderer
|
|
37
|
+
if (blockSchema && Array.isArray(blockSchema.fields)) {
|
|
38
|
+
return (
|
|
39
|
+
<div className="space-y-4 pt-2">
|
|
40
|
+
{blockSchema.fields.map((field: any) => {
|
|
41
|
+
// Evaluate condition if present
|
|
42
|
+
if (field.admin?.condition) {
|
|
43
|
+
if (typeof field.admin.condition === "function") {
|
|
44
|
+
try {
|
|
45
|
+
// Compatibility wrapper: pass { values: data, ...data } to support both old and new signatures
|
|
46
|
+
const evalData = { values: data, ...data };
|
|
47
|
+
const shouldShow = field.admin.condition(evalData, evalData);
|
|
48
|
+
if (!shouldShow) return null;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.warn(`Condition error for field ${field.name}:`, e);
|
|
51
|
+
}
|
|
52
|
+
} else if (typeof field.admin.condition === "object") {
|
|
53
|
+
try {
|
|
54
|
+
const cond = field.admin.condition as any;
|
|
55
|
+
const targetField = cond.field;
|
|
56
|
+
const val = data[targetField];
|
|
57
|
+
let shouldShow = true;
|
|
58
|
+
if ("equals" in cond) {
|
|
59
|
+
shouldShow = val === cond.equals;
|
|
60
|
+
} else if ("notEquals" in cond) {
|
|
61
|
+
shouldShow = val !== cond.notEquals;
|
|
62
|
+
} else if ("in" in cond && Array.isArray(cond.in)) {
|
|
63
|
+
shouldShow = cond.in.includes(val);
|
|
64
|
+
}
|
|
65
|
+
if (!shouldShow) return null;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn(`Declarative condition error for field ${field.name}:`, e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
230
70
|
}
|
|
231
|
-
selectedId={data.selectedId}
|
|
232
|
-
labelField={data.labelField || "title"}
|
|
233
|
-
onChange={handleChange}
|
|
234
|
-
/>
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
case "hero":
|
|
238
|
-
return (
|
|
239
|
-
<div className="space-y-3">
|
|
240
|
-
<HeroField
|
|
241
|
-
heading={data.heading || ""}
|
|
242
|
-
subheading={data.subheading || ""}
|
|
243
|
-
ctaText={data.ctaText || ""}
|
|
244
|
-
ctaUrl={data.ctaUrl || ""}
|
|
245
|
-
onChange={handleChange}
|
|
246
|
-
compact
|
|
247
|
-
/>
|
|
248
|
-
|
|
249
|
-
<div className="grid grid-cols-2 gap-2">
|
|
250
|
-
<UploadField
|
|
251
|
-
field={{ label: "Background", name: "bgImage", maxCount: 1 }}
|
|
252
|
-
value={data.bgImage}
|
|
253
|
-
onChange={(v) => handleChange("bgImage", v)}
|
|
254
|
-
/>
|
|
255
|
-
<input
|
|
256
|
-
type="url"
|
|
257
|
-
value={data.videoUrl || ""}
|
|
258
|
-
onChange={(e) => handleChange("videoUrl", e.target.value)}
|
|
259
|
-
className="w-full px-2.5 py-1.5 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-sm text-[var(--kyro-text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent font-mono text-xs"
|
|
260
|
-
placeholder="Video URL..."
|
|
261
|
-
/>
|
|
262
|
-
</div>
|
|
263
|
-
|
|
264
|
-
<div className="pt-2 border-t border-[var(--kyro-border)]">
|
|
265
|
-
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
266
|
-
Children ({children.length})
|
|
267
|
-
</label>
|
|
268
|
-
<ChildBlocksTree
|
|
269
|
-
blockId={block.id}
|
|
270
|
-
children={children}
|
|
271
|
-
onUpdateChildren={handleUpdateChildren}
|
|
272
|
-
/>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
case "array":
|
|
278
|
-
return (
|
|
279
|
-
<div className="space-y-3">
|
|
280
|
-
<ArrayField
|
|
281
|
-
items={Array.isArray(data.items) ? data.items : []}
|
|
282
|
-
onChange={(items) => handleChange("items", items)}
|
|
283
|
-
compact
|
|
284
|
-
/>
|
|
285
|
-
<div className="pt-2 border-t border-[var(--kyro-border)]">
|
|
286
|
-
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
287
|
-
Children ({children.length})
|
|
288
|
-
</label>
|
|
289
|
-
<ChildBlocksTree
|
|
290
|
-
blockId={block.id}
|
|
291
|
-
children={children}
|
|
292
|
-
onUpdateChildren={handleUpdateChildren}
|
|
293
|
-
/>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
71
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
case "columns":
|
|
317
|
-
return (
|
|
318
|
-
<ColumnsField
|
|
319
|
-
columns={data.columns || 2}
|
|
320
|
-
columnData={data.columnData || []}
|
|
321
|
-
onColumnsChange={(c) => {
|
|
322
|
-
const columnData = data.columnData || [];
|
|
323
|
-
const newColumnData = Array.from({ length: c }, (_, i) => ({
|
|
324
|
-
id: i,
|
|
325
|
-
children: columnData[i]?.children || [],
|
|
326
|
-
}));
|
|
327
|
-
updateBlock(block.id, {
|
|
328
|
-
data: { ...data, columns: c, columnData: newColumnData },
|
|
329
|
-
});
|
|
330
|
-
}}
|
|
331
|
-
onUpdateColumnChildren={handleUpdateColumnChildren}
|
|
332
|
-
/>
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
default:
|
|
336
|
-
return (
|
|
337
|
-
<div className="text-center py-8 text-[var(--kyro-text-muted)]">
|
|
338
|
-
No editor for "{block.type}"
|
|
339
|
-
</div>
|
|
340
|
-
);
|
|
72
|
+
const value = data[field.name];
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
key={field.name}
|
|
76
|
+
className="kyro-block-field-row border-b border-[var(--kyro-border)]/30 pb-3 last:border-b-0 last:pb-0"
|
|
77
|
+
>
|
|
78
|
+
<FieldRenderer
|
|
79
|
+
field={field}
|
|
80
|
+
value={value}
|
|
81
|
+
onChange={(val) => handleChange(field.name, val)}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
})}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
341
88
|
}
|
|
89
|
+
|
|
90
|
+
// Fallback if no schema is provided (for safety with legacy records)
|
|
91
|
+
return (
|
|
92
|
+
<div className="text-center py-8 text-[var(--kyro-text-muted)] text-sm italic">
|
|
93
|
+
No schema defined for block type "{block.type}"
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
342
96
|
};
|
|
343
97
|
|
|
98
|
+
const theme = blockTheme[block.type as string] || blockTheme.default;
|
|
99
|
+
|
|
344
100
|
return (
|
|
345
101
|
<SlidePanel
|
|
346
102
|
open={true}
|
|
347
103
|
onClose={onClose}
|
|
348
|
-
title={`Edit ${block.type}`}
|
|
104
|
+
title={`Edit ${blockSchema?.label || block.type}`}
|
|
349
105
|
width="xl"
|
|
106
|
+
showOverlay={false}
|
|
107
|
+
accentClass={theme.border}
|
|
350
108
|
>
|
|
351
|
-
<div className="space-y-4">
|
|
109
|
+
<div className="space-y-4">
|
|
110
|
+
{renderFields()}
|
|
111
|
+
|
|
112
|
+
{children.length > 0 && (
|
|
113
|
+
<div className="pt-4 border-t border-[var(--kyro-border)]">
|
|
114
|
+
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
115
|
+
Children ({children.length})
|
|
116
|
+
</label>
|
|
117
|
+
<ChildBlocksTree
|
|
118
|
+
blockId={block.id}
|
|
119
|
+
children={children}
|
|
120
|
+
onUpdateChildren={handleUpdateChildren}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
352
125
|
<div className="mt-6 pt-4 border-t border-[var(--kyro-border)]">
|
|
353
126
|
<button
|
|
354
127
|
type="button"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
6
|
+
import { CardField } from "../fields/CardField";
|
|
7
|
+
import { BlockWrapper } from "./BlockWrapper";
|
|
8
|
+
|
|
9
|
+
export const CardBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data || block.data || {};
|
|
17
|
+
|
|
18
|
+
const handleChange = (field: string, value: unknown) => {
|
|
19
|
+
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<BlockWrapper id={block.id} type="card" label="Card">
|
|
24
|
+
<CardField
|
|
25
|
+
title={data.title || ""}
|
|
26
|
+
description={data.description || ""}
|
|
27
|
+
icon={data.icon || ""}
|
|
28
|
+
link={data.link || ""}
|
|
29
|
+
linkText={data.linkText || ""}
|
|
30
|
+
onChange={handleChange}
|
|
31
|
+
compact
|
|
32
|
+
/>
|
|
33
|
+
</BlockWrapper>
|
|
34
|
+
);
|
|
35
|
+
};
|