@kyro-cms/admin 0.12.5 → 0.12.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 (54) hide show
  1. package/dist/index.cjs +32 -110
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.cts +1 -3
  4. package/dist/index.d.ts +1 -3
  5. package/dist/index.js +32 -110
  6. package/package.json +7 -2
  7. package/src/components/AutoForm.tsx +35 -45
  8. package/src/components/DashboardMetrics.tsx +519 -290
  9. package/src/components/FieldRenderer.tsx +59 -47
  10. package/src/components/ListView.tsx +10 -5
  11. package/src/components/PluginsManager.tsx +37 -20
  12. package/src/components/Sidebar.astro +7 -14
  13. package/src/components/blocks/AccordionBlock.tsx +8 -4
  14. package/src/components/blocks/ArrayBlock.tsx +4 -3
  15. package/src/components/blocks/BlockEditModal.tsx +4 -3
  16. package/src/components/blocks/CardBlock.tsx +10 -2
  17. package/src/components/blocks/ChildBlocksTree.tsx +19 -18
  18. package/src/components/blocks/CodeBlock.tsx +7 -2
  19. package/src/components/blocks/FileBlock.tsx +6 -2
  20. package/src/components/blocks/GenericBlock.tsx +1 -1
  21. package/src/components/blocks/HeadingBlock.tsx +6 -2
  22. package/src/components/blocks/HeadingSubheadingBlock.tsx +7 -2
  23. package/src/components/blocks/HeroBlock.tsx +12 -3
  24. package/src/components/blocks/ImageBlock.tsx +8 -2
  25. package/src/components/blocks/ListBlock.tsx +6 -2
  26. package/src/components/blocks/ParagraphBlock.tsx +2 -2
  27. package/src/components/blocks/RelationshipBlock.tsx +10 -2
  28. package/src/components/blocks/VideoBlock.tsx +7 -2
  29. package/src/components/fields/AccordionField.tsx +1 -1
  30. package/src/components/fields/ArrayLayout.tsx +55 -58
  31. package/src/components/fields/BlocksField.tsx +1 -1
  32. package/src/components/fields/ChildrenField.tsx +3 -2
  33. package/src/components/fields/CodeField.tsx +3 -2
  34. package/src/components/fields/ColumnsField.tsx +3 -2
  35. package/src/components/fields/DateField.tsx +2 -2
  36. package/src/components/fields/FieldLayout.tsx +3 -3
  37. package/src/components/fields/GroupLayout.tsx +2 -2
  38. package/src/components/fields/MarkdownField.tsx +2 -2
  39. package/src/components/fields/NumberField.tsx +4 -4
  40. package/src/components/fields/RelationshipField.tsx +4 -1
  41. package/src/components/fields/RichTextField.tsx +200 -5
  42. package/src/components/fields/extensions/blockComponents.tsx +1 -1
  43. package/src/components/fields/extensions/blocksStore.ts +15 -12
  44. package/src/components/ui/Pagination.tsx +1 -1
  45. package/src/fields/index.ts +1 -1
  46. package/src/hooks/useAutoFormState.ts +7 -6
  47. package/src/index.ts +0 -1
  48. package/src/integration.ts +64 -26
  49. package/src/lib/api.ts +1 -0
  50. package/src/lib/config.ts +6 -19
  51. package/src/lib/core-types.ts +78 -0
  52. package/src/plugins/seo-admin.tsx +155 -0
  53. package/src/styles/main.css +2 -0
  54. package/src/vite-env.d.ts +10 -1
@@ -1,5 +1,17 @@
1
1
  import React from "react";
2
- import type { Field } from "@kyro-cms/core/client";
2
+ import type {
3
+ Field,
4
+ TextField as TextFieldType,
5
+ NumberField as NumberFieldType,
6
+ CheckboxField as CheckboxFieldType,
7
+ SelectField as SelectFieldType,
8
+ DateField as DateFieldType,
9
+ CodeField as CodeFieldType,
10
+ UploadField as UploadFieldType,
11
+ RelationshipField as RelationshipFieldType,
12
+ BlocksField as BlocksFieldType,
13
+ MarkdownField as MarkdownFieldType,
14
+ } from "@kyro-cms/core/client";
3
15
  import { UploadField } from "./fields/UploadField";
4
16
  import { CodeField } from "./fields";
5
17
  import NumberField from "./fields/NumberField";
@@ -22,8 +34,8 @@ import { setChangeSource } from "../lib/change-source";
22
34
 
23
35
  interface FieldRendererProps {
24
36
  field: Field;
25
- value: any;
26
- onChange: (value: any) => void;
37
+ value: unknown;
38
+ onChange: (value: unknown) => void;
27
39
  error?: string;
28
40
  disabled?: boolean;
29
41
  formData?: Record<string, unknown>;
@@ -56,9 +68,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
56
68
  case "url":
57
69
  return (
58
70
  <TextField
59
- field={field as any}
60
- value={value}
61
- onChange={onChangeKeystroke}
71
+ field={field as TextFieldType}
72
+ value={(value as string) ?? undefined}
73
+ onChange={onChangeKeystroke as (value: string) => void}
62
74
  error={error}
63
75
  disabled={disabled}
64
76
  />
@@ -67,8 +79,8 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
67
79
  return (
68
80
  <IconField
69
81
  field={field as any}
70
- value={value}
71
- onChange={onChangeKeystroke}
82
+ value={(value as string) ?? undefined}
83
+ onChange={onChangeKeystroke as (value: string) => void}
72
84
  error={error}
73
85
  disabled={disabled}
74
86
  />
@@ -76,9 +88,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
76
88
  case "textarea":
77
89
  return (
78
90
  <TextField
79
- field={{ ...field, variant: "textarea" } as any}
80
- value={value}
81
- onChange={onChangeKeystroke}
91
+ field={{ ...field, variant: "textarea" } as TextFieldType}
92
+ value={(value as string) ?? undefined}
93
+ onChange={onChangeKeystroke as (value: string) => void}
82
94
  error={error}
83
95
  disabled={disabled}
84
96
  />
@@ -86,9 +98,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
86
98
  case "password":
87
99
  return (
88
100
  <TextField
89
- field={{ ...field, variant: "password" } as any}
90
- value={value}
91
- onChange={onChangeKeystroke}
101
+ field={{ ...field, variant: "password" } as TextFieldType}
102
+ value={(value as string) ?? undefined}
103
+ onChange={onChangeKeystroke as (value: string) => void}
92
104
  error={error}
93
105
  disabled={disabled}
94
106
  />
@@ -97,7 +109,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
97
109
  return (
98
110
  <SecretField
99
111
  field={field as any}
100
- value={value as string | null | undefined}
112
+ value={(value as string) ?? undefined}
101
113
  onChange={onChange as (value: string) => void}
102
114
  error={error}
103
115
  disabled={disabled}
@@ -106,9 +118,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
106
118
  case "number":
107
119
  return (
108
120
  <NumberField
109
- field={field as any}
110
- value={value}
111
- onChange={onChange}
121
+ field={field as NumberFieldType}
122
+ value={(value as number) ?? undefined}
123
+ onChange={onChange as (value: number | undefined) => void}
112
124
  disabled={disabled}
113
125
  error={error}
114
126
  />
@@ -116,9 +128,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
116
128
  case "checkbox":
117
129
  return (
118
130
  <CheckboxField
119
- field={field as any}
120
- value={value}
121
- onChange={onChange}
131
+ field={field as CheckboxFieldType}
132
+ value={value as boolean}
133
+ onChange={onChange as (value: boolean) => void}
122
134
  disabled={disabled}
123
135
  error={error}
124
136
  />
@@ -126,9 +138,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
126
138
  case "select":
127
139
  return (
128
140
  <SelectField
129
- field={field as any}
130
- value={value}
131
- onChange={onChangeKeystroke}
141
+ field={field as SelectFieldType}
142
+ value={(value as string | string[]) ?? undefined}
143
+ onChange={onChangeKeystroke as (value: string | string[] | undefined) => void}
132
144
  error={error}
133
145
  disabled={disabled}
134
146
  formData={formData}
@@ -140,9 +152,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
140
152
  case "date":
141
153
  return (
142
154
  <DateField
143
- field={field as any}
144
- value={value}
145
- onChange={onChange}
155
+ field={field as DateFieldType}
156
+ value={(value as string) ?? undefined}
157
+ onChange={onChange as (value: string | undefined) => void}
146
158
  disabled={disabled}
147
159
  error={error}
148
160
  />
@@ -151,7 +163,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
151
163
  return (
152
164
  <RichTextField
153
165
  field={field}
154
- value={value}
166
+ value={value as Record<string, any> | null}
155
167
  onChange={onChangeKeystroke}
156
168
  disabled={disabled}
157
169
  error={error}
@@ -160,9 +172,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
160
172
  case "markdown":
161
173
  return (
162
174
  <MarkdownField
163
- field={field as any}
164
- value={value}
165
- onChange={onChangeKeystroke}
175
+ field={field as MarkdownFieldType}
176
+ value={(value as string) ?? undefined}
177
+ onChange={onChangeKeystroke as (value: string) => void}
166
178
  disabled={disabled}
167
179
  error={error}
168
180
  />
@@ -170,9 +182,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
170
182
  case "code":
171
183
  return (
172
184
  <CodeField
173
- field={field as any}
174
- value={value}
175
- onChange={onChangeKeystroke}
185
+ field={field as CodeFieldType}
186
+ value={(value as string) ?? undefined}
187
+ onChange={onChangeKeystroke as (value: string) => void}
176
188
  disabled={disabled}
177
189
  error={error}
178
190
  />
@@ -182,9 +194,9 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
182
194
  return (
183
195
  <FieldLayout field={field} error={error}>
184
196
  <UploadField
185
- field={field as any}
186
- value={value}
187
- onChange={onChange}
197
+ field={field as UploadFieldType}
198
+ value={value as Record<string, unknown> | Record<string, unknown>[] | null}
199
+ onChange={onChange as (value: Record<string, unknown> | Record<string, unknown>[] | null) => void}
188
200
  disabled={disabled}
189
201
  />
190
202
  </FieldLayout>
@@ -192,7 +204,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
192
204
  case "relationship":
193
205
  return (
194
206
  <RelationshipField
195
- field={field as any}
207
+ field={field as unknown as { name: string; label?: string; relationTo: string | string[]; hasMany?: boolean; required?: boolean; admin?: { description?: string; readOnly?: boolean; placeholder?: string; } }}
196
208
  value={value}
197
209
  onChange={onChange}
198
210
  disabled={disabled}
@@ -203,8 +215,8 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
203
215
  return (
204
216
  <FieldLayout field={field} error={error}>
205
217
  <ListField
206
- items={Array.isArray(value) ? value : []}
207
- onChange={onChange}
218
+ items={Array.isArray(value) ? (value as string[]) : []}
219
+ onChange={onChange as (items: string[]) => void}
208
220
  compact
209
221
  />
210
222
  </FieldLayout>
@@ -213,8 +225,8 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
213
225
  return (
214
226
  <ArrayLayout
215
227
  field={field}
216
- value={Array.isArray(value) ? value : []}
217
- onChange={onChange}
228
+ value={Array.isArray(value) ? (value as unknown[]) : []}
229
+ onChange={onChange as (value: unknown[]) => void}
218
230
  disabled={disabled}
219
231
  renderField={(nestedField, parentData, onNestedChange) => {
220
232
  const nestedValue = parentData[nestedField.name];
@@ -239,8 +251,8 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
239
251
  case "blocks":
240
252
  return (
241
253
  <BlocksField
242
- field={field as any}
243
- value={value}
254
+ field={field as BlocksFieldType}
255
+ value={value as unknown[]}
244
256
  onChange={onChangeKeystroke}
245
257
  disabled={disabled}
246
258
  error={error}
@@ -251,7 +263,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
251
263
  <GroupLayout
252
264
  field={field}
253
265
  value={value as Record<string, unknown> | null}
254
- onChange={onChange}
266
+ onChange={onChange as (value: Record<string, unknown>) => void}
255
267
  renderField={(nestedField, parentData, onNestedChange) => {
256
268
  const nestedValue = parentData[nestedField.name];
257
269
  return (
@@ -280,7 +292,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
280
292
  <div className="flex items-center gap-3">
281
293
  <input
282
294
  type="color"
283
- value={value || "#000000"}
295
+ value={(value as string) || "#000000"}
284
296
  onChange={(e) => onChange(e.target.value)}
285
297
  disabled={disabled}
286
298
  className="h-10 w-14 p-1 cursor-pointer bg-[var(--kyro-input-bg)] border border-[var(--kyro-input-border)] rounded-lg"
@@ -288,7 +300,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
288
300
  <input
289
301
  type="text"
290
302
  className="kyro-form-input font-mono "
291
- value={value || ""}
303
+ value={(value as string) || ""}
292
304
  onChange={(e) => onChange(e.target.value)}
293
305
  disabled={disabled}
294
306
  placeholder="#000000"
@@ -259,6 +259,7 @@ export function ListView({
259
259
  const params = new URLSearchParams({
260
260
  page: page.toString(),
261
261
  limit: limit.toString(),
262
+ depth: "1",
262
263
  });
263
264
 
264
265
  if (search) params.append("search", search);
@@ -764,17 +765,21 @@ function formatCellValue(value: any, type?: string, t?: any): string {
764
765
  if (Array.isArray(value)) {
765
766
  return value.map(item => {
766
767
  if (item && typeof item === "object") {
767
- return item.title || item.name || item.email || item.filename || item.url || JSON.stringify(item).slice(0, 30);
768
+ const target = ('value' in item && typeof item.value === 'object' && item.value !== null) ? item.value : item;
769
+ const fallbackName = (target.firstName || target.lastName) ? `${target.firstName || ''} ${target.lastName || ''}`.trim() : undefined;
770
+ return target.fullName || fallbackName || target.title || target.name || target.label || target.email || target.filename || target.slug || target.id || JSON.stringify(target).slice(0, 30);
768
771
  }
769
772
  return String(item ?? "").slice(0, 30);
770
773
  }).filter(Boolean).join(", ");
771
774
  }
772
775
 
773
776
  if (typeof value === "object") {
774
- if (value.title) return value.title;
775
- if (value.name) return value.name;
776
- if (value.email) return value.email;
777
- return JSON.stringify(value).slice(0, 50);
777
+ const target = ('value' in value && typeof value.value === 'object' && value.value !== null) ? value.value : value;
778
+ const fallbackName = (target.firstName || target.lastName) ? `${target.firstName || ''} ${target.lastName || ''}`.trim() : undefined;
779
+ const label = target.fullName || fallbackName || target.title || target.name || target.label || target.email || target.filename || target.slug;
780
+ if (label) return String(label);
781
+ if (target.id) return String(target.id);
782
+ return JSON.stringify(target).slice(0, 50);
778
783
  }
779
784
  return String(value).slice(0, 60);
780
785
  }
@@ -1,4 +1,5 @@
1
- import React, { useState, useEffect } from "react";
1
+ import { pluginViews } from "virtual:kyro-plugins";
2
+ import React, { useState, useEffect, Suspense } from "react";
2
3
  import {
3
4
  Blocks,
4
5
  Settings,
@@ -16,6 +17,7 @@ import { PageHeader } from "./ui/PageHeader";
16
17
  import { Badge } from "./ui/Badge";
17
18
  import { useTranslation } from "react-i18next";
18
19
 
20
+
19
21
  interface Plugin {
20
22
  id: string;
21
23
  name: string;
@@ -32,13 +34,15 @@ interface ToggleError {
32
34
  }
33
35
 
34
36
  export function PluginsManager() {
35
- const { t } = useTranslation();
37
+ const { t } = useTranslation();
36
38
  const [plugins, setPlugins] = useState<Plugin[]>([]);
37
39
  const [loading, setLoading] = useState(true);
38
40
  const [searchQuery, setSearchQuery] = useState("");
39
41
  const [showConfigModal, setShowConfigModal] = useState<string | null>(null);
40
42
  const [error, setError] = useState<string | null>(null);
41
43
 
44
+ const PluginSettingsView = showConfigModal ? pluginViews[showConfigModal] : null;
45
+
42
46
  const fetchPlugins = async () => {
43
47
  try {
44
48
  setLoading(true);
@@ -120,8 +124,8 @@ export function PluginsManager() {
120
124
  <div>
121
125
  <h4 className="text-sm font-bold text-blue-500 mb-1">Developer-First Approach</h4>
122
126
  <p className="text-xs text-blue-500/80 leading-relaxed">
123
- Plugins inject core structural components like schemas, collections, and APIs.
124
- To ensure version-controlled integrity, plugins are entirely managed via code in your <code className="bg-blue-500/20 px-1.5 py-0.5 rounded-md text-[10px] font-mono">kyro.config.ts</code> file.
127
+ Plugins inject core structural components like schemas, collections, and APIs.
128
+ To ensure version-controlled integrity, plugins are entirely managed via code in your <code className="bg-blue-500/20 px-1.5 py-0.5 rounded-md text-[10px] font-mono">kyro.config.ts</code> file.
125
129
  This page serves as a read-only directory of the currently installed ecosystem.
126
130
  </p>
127
131
  </div>
@@ -222,14 +226,16 @@ export function PluginsManager() {
222
226
  </div>
223
227
 
224
228
  <div className="flex items-center gap-1.5">
225
- <button
226
- type="button"
227
- onClick={() => setShowConfigModal(plugin.id)}
228
- className="p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg hover:border-[var(--kyro-primary)] transition-all disabled:opacity-30 shadow-sm"
229
- disabled={!plugin.enabled}
230
- >
231
- <Settings className="w-3.5 h-3.5" />
232
- </button>
229
+ {pluginViews[plugin.id] && (
230
+ <button
231
+ type="button"
232
+ onClick={() => setShowConfigModal(plugin.id)}
233
+ className="p-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-lg hover:border-[var(--kyro-primary)] transition-all disabled:opacity-30 shadow-sm"
234
+ disabled={!plugin.enabled}
235
+ >
236
+ <Settings className="w-3.5 h-3.5" />
237
+ </button>
238
+ )}
233
239
  </div>
234
240
  </div>
235
241
  </div>
@@ -253,15 +259,26 @@ export function PluginsManager() {
253
259
  size="lg"
254
260
  >
255
261
  <ModalContent>
256
- <div className="p-12 text-center space-y-4">
257
- <div className="w-20 h-20 mx-auto bg-[var(--kyro-surface-accent)] rounded-3xl flex items-center justify-center border border-[var(--kyro-border)] shadow-xl">
258
- <Settings className="w-10 h-10 text-[var(--kyro-primary)] opacity-40 animate-spin-slow" />
259
- </div>
260
- <div>
261
- <h4 className="text-xl font-bold mb-1">Advanced Configuration</h4>
262
- <p className="text-sm text-[var(--kyro-text-secondary)] opacity-50 italic">Settings for this plugin will be synthesized dynamically from its manifest.</p>
262
+ {PluginSettingsView ? (
263
+ <Suspense fallback={
264
+ <div className="p-12 flex flex-col items-center justify-center space-y-4">
265
+ <RefreshCw className="w-8 h-8 text-[var(--kyro-primary)] animate-spin opacity-50" />
266
+ <p className="text-sm text-[var(--kyro-text-secondary)]">Loading plugin settings...</p>
267
+ </div>
268
+ }>
269
+ <PluginSettingsView />
270
+ </Suspense>
271
+ ) : (
272
+ <div className="p-12 text-center space-y-4">
273
+ <div className="w-20 h-20 mx-auto bg-[var(--kyro-surface-accent)] rounded-3xl flex items-center justify-center border border-[var(--kyro-border)] shadow-xl">
274
+ <Settings className="w-10 h-10 text-[var(--kyro-primary)] opacity-40 animate-spin-slow" />
275
+ </div>
276
+ <div>
277
+ <h4 className="text-xl font-bold mb-1">Custom Configuration</h4>
278
+ <p className="text-sm text-[var(--kyro-text-secondary)] opacity-50 italic">Settings for this plugin are managed by its own custom views and components.</p>
279
+ </div>
263
280
  </div>
264
- </div>
281
+ )}
265
282
  </ModalContent>
266
283
  <ModalActions>
267
284
  <button
@@ -10,6 +10,7 @@ interface NavItem {
10
10
  href: string;
11
11
  label: string;
12
12
  icon: string;
13
+ order?: number;
13
14
  }
14
15
 
15
16
  const siteSettings = await getSiteSettings({ request: Astro.request });
@@ -33,7 +34,6 @@ const { title } = Astro.props;
33
34
  const currentPath = Astro.url.pathname;
34
35
 
35
36
  const groupedCollections = new Map<string, NavItem[]>();
36
- const defaultCollections: NavItem[] = [];
37
37
 
38
38
  nonAuthCollections
39
39
  .filter((col) => col.slug !== "media") //removed here cos it's been added manually in the navSections.
@@ -42,22 +42,19 @@ nonAuthCollections
42
42
  href: `${adminPath}/${col.slug}`,
43
43
  label: col.label || col.slug,
44
44
  icon: (col.admin?.icon as string) || "collection",
45
+ order: typeof col.admin?.order === 'number' ? col.admin.order : 999,
45
46
  };
46
47
 
47
- if (col.admin?.group) {
48
- const groupName = col.admin.group as string;
49
- if (!groupedCollections.has(groupName)) {
50
- groupedCollections.set(groupName, []);
51
- }
52
- groupedCollections.get(groupName)!.push(item);
53
- } else {
54
- defaultCollections.push(item);
48
+ const groupName = (col.admin?.group as string) || "Content";
49
+ if (!groupedCollections.has(groupName)) {
50
+ groupedCollections.set(groupName, []);
55
51
  }
52
+ groupedCollections.get(groupName)!.push(item);
56
53
  });
57
54
 
58
55
  const dynamicSections = Array.from(groupedCollections.entries()).map(([group, items]) => ({
59
56
  label: group,
60
- items: items,
57
+ items: items.sort((a, b) => (a.order || 999) - (b.order || 999)),
61
58
  }));
62
59
 
63
60
  const navSections: { label: string; items: NavItem[] }[] = [
@@ -73,10 +70,6 @@ const navSections: { label: string; items: NavItem[] }[] = [
73
70
  ],
74
71
  },
75
72
  ...dynamicSections,
76
- ...(defaultCollections.length > 0 ? [{
77
- label: "Content",
78
- items: defaultCollections,
79
- }] : []),
80
73
  {
81
74
  label: "Settings",
82
75
  items: [
@@ -4,10 +4,14 @@ import {
4
4
  useBlockActions,
5
5
  } from "../fields/extensions/blocksStore";
6
6
  import { ChevronRight, X } from "../ui/icons";
7
- import { AccordionField } from "../fields/AccordionField";
7
+ import { AccordionField, type AccordionItem } from "../fields/AccordionField";
8
8
  import { useTranslation } from "react-i18next";
9
9
 
10
- export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
10
+ interface AccordionBlockData {
11
+ items?: AccordionItem[];
12
+ }
13
+
14
+ export const AccordionBlock: React.FC<{ block: { id: string; data?: Record<string, unknown> }; index: number }> = ({
11
15
  block,
12
16
  index,
13
17
  }) => {
@@ -15,10 +19,10 @@ export const AccordionBlock: React.FC<{ block: Record<string, unknown>; index: n
15
19
  const blockData = useBlockById(block.id);
16
20
  const { updateBlock, removeBlock, moveBlock } = useBlockActions();
17
21
 
18
- const data = blockData?.data ?? block.data ?? {};
22
+ const data = (blockData?.data ?? block.data ?? {}) as AccordionBlockData;
19
23
  const items = Array.isArray(data.items) ? data.items : [];
20
24
 
21
- const handleChange = (items: Record<string, unknown>[]) => {
25
+ const handleChange = (items: AccordionItem[]) => {
22
26
  updateBlock(block.id, { data: { ...data, items } });
23
27
  };
24
28
 
@@ -6,8 +6,9 @@ import {
6
6
  import { ChevronRight, X } from "../ui/icons";
7
7
  import { ChildBlocksTree } from "./ChildBlocksTree";
8
8
  import { useTranslation } from "react-i18next";
9
+ import type { BlockData } from "@kyro-cms/core/client";
9
10
 
10
- export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
11
+ export const ArrayBlock: React.FC<{ block: { id: string; data?: Record<string, unknown>; children?: BlockData[] }; index: number }> = ({
11
12
  block,
12
13
  index,
13
14
  }) => {
@@ -15,8 +16,8 @@ export const ArrayBlock: React.FC<{ block: Record<string, unknown>; index: numbe
15
16
  const blockData = useBlockById(block.id);
16
17
  const { updateBlock, removeBlock, moveBlock } = useBlockActions();
17
18
 
18
- const data = blockData?.data ?? block.data ?? {};
19
- const children = blockData?.children ?? block.children ?? [];
19
+ const data = (blockData?.data ?? block.data ?? {}) as Record<string, unknown>;
20
+ const children = (blockData?.children ?? block.children ?? []) as BlockData[];
20
21
 
21
22
  return (
22
23
  <div className="block-array border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
@@ -7,9 +7,10 @@ import { blockTheme } from "../fields/extensions/blockComponents";
7
7
  import { SlidePanel } from "../ui/SlidePanel";
8
8
  import { ChildBlocksTree } from "./ChildBlocksTree";
9
9
  import { FieldRenderer } from "../FieldRenderer";
10
+ import type { BlockData } from "@kyro-cms/core/client";
10
11
 
11
12
  interface BlockEditModalProps {
12
- block: Record<string, unknown>;
13
+ block: Record<string, any>;
13
14
  blockSchema?: Record<string, any>;
14
15
  onClose: () => void;
15
16
  }
@@ -28,7 +29,7 @@ export const BlockEditModal: React.FC<BlockEditModalProps> = ({
28
29
  updateBlock(block.id, { data: { ...data, [field]: value } });
29
30
  };
30
31
 
31
- const handleUpdateChildren = (newChildren: Record<string, unknown>[]) => {
32
+ const handleUpdateChildren = (newChildren: BlockData[]) => {
32
33
  updateBlock(block.id, { children: newChildren });
33
34
  };
34
35
 
@@ -95,7 +96,7 @@ export const BlockEditModal: React.FC<BlockEditModalProps> = ({
95
96
  );
96
97
  };
97
98
 
98
- const theme = blockTheme[block.type as string] || blockTheme.default;
99
+ const theme = blockTheme[block.type] || blockTheme.default;
99
100
 
100
101
  return (
101
102
  <SlidePanel
@@ -6,14 +6,22 @@ import {
6
6
  import { CardField } from "../fields/CardField";
7
7
  import { BlockWrapper } from "./BlockWrapper";
8
8
 
9
- export const CardBlock: React.FC<{ block: Record<string, unknown>; index: number }> = ({
9
+ interface CardBlockData {
10
+ title?: string;
11
+ description?: string;
12
+ icon?: string;
13
+ link?: string;
14
+ linkText?: string;
15
+ }
16
+
17
+ export const CardBlock: React.FC<{ block: { id: string; data?: Record<string, unknown> }; index: number }> = ({
10
18
  block,
11
19
  index,
12
20
  }) => {
13
21
  const blockData = useBlockById(block.id);
14
22
  const { updateBlock } = useBlockActions();
15
23
 
16
- const data = blockData?.data || block.data || {};
24
+ const data = (blockData?.data ?? block.data ?? {}) as CardBlockData;
17
25
 
18
26
  const handleChange = (field: string, value: unknown) => {
19
27
  updateBlock(block.id, { data: { ...data, [field]: value } });