@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.
- package/dist/index.cjs +32 -110
- package/dist/index.css +1 -1
- package/dist/index.d.cts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +32 -110
- package/package.json +7 -2
- package/src/components/AutoForm.tsx +35 -45
- package/src/components/DashboardMetrics.tsx +519 -290
- package/src/components/FieldRenderer.tsx +59 -47
- package/src/components/ListView.tsx +10 -5
- package/src/components/PluginsManager.tsx +37 -20
- package/src/components/Sidebar.astro +7 -14
- package/src/components/blocks/AccordionBlock.tsx +8 -4
- package/src/components/blocks/ArrayBlock.tsx +4 -3
- package/src/components/blocks/BlockEditModal.tsx +4 -3
- package/src/components/blocks/CardBlock.tsx +10 -2
- package/src/components/blocks/ChildBlocksTree.tsx +19 -18
- package/src/components/blocks/CodeBlock.tsx +7 -2
- package/src/components/blocks/FileBlock.tsx +6 -2
- package/src/components/blocks/GenericBlock.tsx +1 -1
- package/src/components/blocks/HeadingBlock.tsx +6 -2
- package/src/components/blocks/HeadingSubheadingBlock.tsx +7 -2
- package/src/components/blocks/HeroBlock.tsx +12 -3
- package/src/components/blocks/ImageBlock.tsx +8 -2
- package/src/components/blocks/ListBlock.tsx +6 -2
- package/src/components/blocks/ParagraphBlock.tsx +2 -2
- package/src/components/blocks/RelationshipBlock.tsx +10 -2
- package/src/components/blocks/VideoBlock.tsx +7 -2
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayLayout.tsx +55 -58
- package/src/components/fields/BlocksField.tsx +1 -1
- package/src/components/fields/ChildrenField.tsx +3 -2
- package/src/components/fields/CodeField.tsx +3 -2
- package/src/components/fields/ColumnsField.tsx +3 -2
- package/src/components/fields/DateField.tsx +2 -2
- package/src/components/fields/FieldLayout.tsx +3 -3
- package/src/components/fields/GroupLayout.tsx +2 -2
- package/src/components/fields/MarkdownField.tsx +2 -2
- package/src/components/fields/NumberField.tsx +4 -4
- package/src/components/fields/RelationshipField.tsx +4 -1
- package/src/components/fields/RichTextField.tsx +200 -5
- package/src/components/fields/extensions/blockComponents.tsx +1 -1
- package/src/components/fields/extensions/blocksStore.ts +15 -12
- package/src/components/ui/Pagination.tsx +1 -1
- package/src/fields/index.ts +1 -1
- package/src/hooks/useAutoFormState.ts +7 -6
- package/src/index.ts +0 -1
- package/src/integration.ts +64 -26
- package/src/lib/api.ts +1 -0
- package/src/lib/config.ts +6 -19
- package/src/lib/core-types.ts +78 -0
- package/src/plugins/seo-admin.tsx +155 -0
- package/src/styles/main.css +2 -0
- package/src/vite-env.d.ts +10 -1
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type {
|
|
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:
|
|
26
|
-
onChange: (value:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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 } });
|