@kyro-cms/admin 0.1.7 → 0.1.9
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/package.json +7 -2
- package/src/components/Admin.tsx +1 -1
- package/src/components/AutoForm.tsx +966 -337
- package/src/components/CreateView.tsx +1 -1
- package/src/components/DetailView.tsx +1 -1
- package/src/components/EnhancedListView.tsx +156 -52
- package/src/components/ListView.tsx +1 -1
- package/src/components/Modal.tsx +65 -8
- package/src/components/Sidebar.astro +2 -2
- package/src/components/ThemeProvider.tsx +8 -2
- package/src/components/blocks/AccordionBlock.tsx +20 -52
- package/src/components/blocks/ArrayBlock.tsx +40 -31
- package/src/components/blocks/BlockEditModal.tsx +170 -581
- package/src/components/blocks/ButtonBlock.tsx +27 -128
- package/src/components/blocks/CodeBlock.tsx +88 -40
- package/src/components/blocks/ColumnsBlock.tsx +27 -85
- package/src/components/blocks/FileBlock.tsx +38 -39
- package/src/components/blocks/HeadingBlock.tsx +9 -31
- package/src/components/blocks/HeroBlock.tsx +42 -100
- package/src/components/blocks/ImageBlock.tsx +6 -7
- package/src/components/blocks/LinkBlock.tsx +27 -33
- package/src/components/blocks/ListBlock.tsx +47 -26
- package/src/components/blocks/RelationshipBlock.tsx +26 -233
- package/src/components/blocks/RichTextBlock.tsx +66 -0
- package/src/components/blocks/VStackBlock.tsx +23 -37
- package/src/components/blocks/VideoBlock.tsx +52 -32
- package/src/components/fields/AccordionField.tsx +213 -0
- package/src/components/fields/ArrayField.tsx +241 -0
- package/src/components/fields/BlocksField.tsx +5 -5
- package/src/components/fields/ButtonField.tsx +53 -0
- package/src/components/fields/CheckboxField.tsx +7 -3
- package/src/components/fields/ChildrenField.tsx +48 -0
- package/src/components/fields/CodeField.tsx +154 -94
- package/src/components/fields/ColumnsField.tsx +137 -0
- package/src/components/fields/DateField.tsx +9 -24
- package/src/components/fields/EditorClient.tsx +426 -160
- package/src/components/fields/HeadingField.tsx +31 -0
- package/src/components/fields/HeroField.tsx +101 -0
- package/src/components/fields/JSONField.tsx +7 -27
- package/src/components/fields/LinkField.tsx +81 -0
- package/src/components/fields/ListField.tsx +74 -0
- package/src/components/fields/MarkdownField.tsx +4 -26
- package/src/components/fields/NumberField.tsx +9 -27
- package/src/components/fields/PortableTextField.tsx +61 -49
- package/src/components/fields/RelationshipBlockField.tsx +233 -0
- package/src/components/fields/RelationshipField.tsx +59 -13
- package/src/components/fields/SelectField.tsx +6 -4
- package/src/components/fields/TextField.tsx +9 -24
- package/src/components/fields/UploadField.tsx +613 -0
- package/src/components/fields/VideoField.tsx +73 -0
- package/src/components/fields/extensions/blockComponents.tsx +11 -1
- package/src/components/fields/extensions/blocksStore.ts +1 -1
- package/src/components/fields/index.ts +12 -1
- package/src/components/layout/Layout.tsx +1 -1
- package/src/lib/api.ts +163 -0
- package/src/lib/config.ts +1 -1
- package/src/lib/dataStore.ts +87 -30
- package/src/lib/date-utils.ts +69 -0
- package/src/lib/db/version-adapter.ts +248 -0
- package/src/lib/i18n.tsx +353 -0
- package/src/lib/slugify.ts +15 -0
- package/src/lib/validation.ts +250 -0
- package/src/pages/api/[collection]/[id]/publish.ts +12 -4
- package/src/pages/api/[collection]/[id]/versions.ts +39 -9
- package/src/pages/api/[collection]/[id].ts +13 -1
- package/src/pages/api/[collection]/index.ts +5 -6
- package/src/styles/main.css +12 -2
- package/src/components/blocks/BlockEditModal.MARKER +0 -12
- package/src/components/fields/FileField.tsx +0 -390
- package/src/components/fields/HybridContentField.tsx +0 -109
- package/src/components/fields/ImageField.tsx +0 -429
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useMemo,
|
|
4
|
-
useEffect,
|
|
5
|
-
useState,
|
|
6
|
-
Suspense,
|
|
7
|
-
lazy,
|
|
8
|
-
} from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useState, Suspense, lazy } from "react";
|
|
9
2
|
import { githubLight } from "@uiw/codemirror-theme-github";
|
|
10
3
|
import { aura } from "@uiw/codemirror-theme-aura";
|
|
11
|
-
import type { CodeField as CodeFieldType } from "@kyro-cms/core";
|
|
4
|
+
import type { CodeField as CodeFieldType } from "@kyro-cms/core/client";
|
|
5
|
+
import { useTheme } from "../ThemeProvider";
|
|
12
6
|
|
|
13
7
|
interface CodeFieldProps {
|
|
14
8
|
field: CodeFieldType;
|
|
@@ -18,27 +12,24 @@ interface CodeFieldProps {
|
|
|
18
12
|
disabled?: boolean;
|
|
19
13
|
}
|
|
20
14
|
|
|
21
|
-
// Lazy-loaded CodeMirror wrapper component
|
|
22
15
|
const CodeMirrorEditor = lazy(() =>
|
|
23
16
|
import("@uiw/react-codemirror").then((mod) => ({ default: mod.default })),
|
|
24
17
|
);
|
|
25
18
|
|
|
26
19
|
const LANGUAGES = [
|
|
27
|
-
{ value: "javascript", label: "JavaScript"
|
|
28
|
-
{ value: "typescript", label: "TypeScript"
|
|
29
|
-
{ value: "json", label: "JSON"
|
|
30
|
-
{ value: "html", label: "HTML"
|
|
31
|
-
{ value: "css", label: "CSS"
|
|
32
|
-
{ value: "sql", label: "SQL"
|
|
33
|
-
{ value: "python", label: "Python"
|
|
34
|
-
{ value: "rust", label: "Rust"
|
|
35
|
-
{ value: "java", label: "Java"
|
|
36
|
-
{ value: "
|
|
37
|
-
{ value: "
|
|
38
|
-
{ value: "markdown", label: "Markdown", ext: "markdown" },
|
|
20
|
+
{ value: "javascript", label: "JavaScript" },
|
|
21
|
+
{ value: "typescript", label: "TypeScript" },
|
|
22
|
+
{ value: "json", label: "JSON" },
|
|
23
|
+
{ value: "html", label: "HTML" },
|
|
24
|
+
{ value: "css", label: "CSS" },
|
|
25
|
+
{ value: "sql", label: "SQL" },
|
|
26
|
+
{ value: "python", label: "Python" },
|
|
27
|
+
{ value: "rust", label: "Rust" },
|
|
28
|
+
{ value: "java", label: "Java" },
|
|
29
|
+
{ value: "php", label: "PHP" },
|
|
30
|
+
{ value: "markdown", label: "Markdown" },
|
|
39
31
|
];
|
|
40
32
|
|
|
41
|
-
// Language extension loader
|
|
42
33
|
const languageExtensions: Record<string, () => Promise<any>> = {
|
|
43
34
|
javascript: () =>
|
|
44
35
|
import("@codemirror/lang-javascript").then((m) =>
|
|
@@ -85,29 +76,28 @@ export const CodeField: React.FC<CodeFieldProps> = ({
|
|
|
85
76
|
const [isDark, setIsDark] = useState(false);
|
|
86
77
|
const [extensions, setExtensions] = useState<any[]>([]);
|
|
87
78
|
const [loading, setLoading] = useState(false);
|
|
79
|
+
const [copied, setCopied] = useState(false);
|
|
80
|
+
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
88
81
|
|
|
82
|
+
const { theme } = useTheme();
|
|
83
|
+
const accent = theme.colors?.accent || theme.colors?.primary || "#6366f1";
|
|
89
84
|
const language = field.language?.toLowerCase() || "javascript";
|
|
90
85
|
|
|
91
86
|
useEffect(() => {
|
|
92
87
|
setIsMounted(true);
|
|
93
88
|
setIsDark(document.documentElement.classList.contains("dark"));
|
|
94
|
-
|
|
95
89
|
const observer = new MutationObserver(() => {
|
|
96
90
|
setIsDark(document.documentElement.classList.contains("dark"));
|
|
97
91
|
});
|
|
98
|
-
|
|
99
92
|
observer.observe(document.documentElement, {
|
|
100
93
|
attributes: true,
|
|
101
94
|
attributeFilter: ["class"],
|
|
102
95
|
});
|
|
103
|
-
|
|
104
96
|
return () => observer.disconnect();
|
|
105
97
|
}, []);
|
|
106
98
|
|
|
107
|
-
// Load language extensions dynamically
|
|
108
99
|
useEffect(() => {
|
|
109
100
|
if (!isMounted) return;
|
|
110
|
-
|
|
111
101
|
const loadExtensions = async () => {
|
|
112
102
|
setLoading(true);
|
|
113
103
|
try {
|
|
@@ -122,113 +112,183 @@ export const CodeField: React.FC<CodeFieldProps> = ({
|
|
|
122
112
|
setLoading(false);
|
|
123
113
|
}
|
|
124
114
|
};
|
|
125
|
-
|
|
126
115
|
loadExtensions();
|
|
127
116
|
}, [language, isMounted]);
|
|
128
117
|
|
|
129
118
|
const handleChange = useCallback(
|
|
130
|
-
(val: string) =>
|
|
131
|
-
onChange?.(val);
|
|
132
|
-
},
|
|
119
|
+
(val: string) => onChange?.(val),
|
|
133
120
|
[onChange],
|
|
134
121
|
);
|
|
135
122
|
|
|
136
|
-
const
|
|
123
|
+
const handleCopy = useCallback(() => {
|
|
124
|
+
navigator.clipboard.writeText(value);
|
|
125
|
+
setCopied(true);
|
|
126
|
+
setTimeout(() => setCopied(false), 1500);
|
|
127
|
+
}, [value]);
|
|
128
|
+
|
|
129
|
+
const toggleFullScreen = useCallback(() => {
|
|
130
|
+
setIsFullScreen((prev) => !prev);
|
|
131
|
+
document.body.style.overflow = !isFullScreen ? "hidden" : "";
|
|
132
|
+
}, [isFullScreen]);
|
|
133
|
+
|
|
134
|
+
const codeMirrorTheme = isDark ? aura : githubLight;
|
|
135
|
+
const langLabel =
|
|
136
|
+
LANGUAGES.find((l) => l.value === language)?.label || language;
|
|
137
|
+
|
|
138
|
+
const hexToRgba = (hex: string, alpha: number = 1) => {
|
|
139
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
140
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
141
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
142
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
143
|
+
};
|
|
137
144
|
|
|
138
145
|
if (!isMounted) {
|
|
139
146
|
return (
|
|
140
|
-
<div className="
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</label>
|
|
147
|
-
<div className="h-[300px] bg-[var(--kyro-surface)] animate-pulse rounded-md" />
|
|
147
|
+
<div className="space-y-3">
|
|
148
|
+
<div className="flex items-center gap-3">
|
|
149
|
+
<div className="h-5 w-24 rounded bg-[var(--kyro-surface-accent)] animate-pulse" />
|
|
150
|
+
<div className="h-5 w-16 rounded bg-[var(--kyro-surface-accent)] animate-pulse" />
|
|
151
|
+
</div>
|
|
152
|
+
<div className="h-[280px] rounded-xl bg-[var(--kyro-surface-accent)] animate-pulse" />
|
|
148
153
|
</div>
|
|
149
154
|
);
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
return (
|
|
153
|
-
<div
|
|
158
|
+
<div
|
|
159
|
+
className="group"
|
|
160
|
+
style={
|
|
161
|
+
{
|
|
162
|
+
"--accent": accent,
|
|
163
|
+
"--accent-light": hexToRgba(accent, 0.1),
|
|
164
|
+
"--accent-light-2": hexToRgba(accent, 0.05),
|
|
165
|
+
} as React.CSSProperties
|
|
166
|
+
}
|
|
167
|
+
>
|
|
168
|
+
{/* Header */}
|
|
154
169
|
<div className="flex items-center justify-between mb-2">
|
|
155
|
-
<label className="kyro-form-label">
|
|
156
|
-
{field.label || field.name}
|
|
157
|
-
{field.required && (
|
|
158
|
-
<span className="kyro-form-label-required">*</span>
|
|
159
|
-
)}
|
|
160
|
-
</label>
|
|
161
|
-
|
|
162
|
-
{/* Language indicator */}
|
|
163
170
|
<div className="flex items-center gap-2">
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
<span className="text-sm font-semibold text-[var(--kyro-text-primary)]">
|
|
172
|
+
{field.label || field.name}
|
|
173
|
+
</span>
|
|
174
|
+
<span
|
|
175
|
+
className="text-[10px] px-1.5 py-0.5 rounded font-semibold transition-colors"
|
|
176
|
+
style={{
|
|
177
|
+
backgroundColor: loading ? "var(--kyro-surface-accent)" : accent,
|
|
178
|
+
color: loading ? "var(--kyro-text-muted)" : "white",
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{loading ? "..." : langLabel}
|
|
171
182
|
</span>
|
|
172
183
|
</div>
|
|
184
|
+
|
|
185
|
+
{/* Action buttons */}
|
|
186
|
+
<div className="flex items-center gap-1">
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
onClick={handleCopy}
|
|
190
|
+
className="text-[10px] px-2 py-0.5 rounded font-medium transition-all"
|
|
191
|
+
style={{
|
|
192
|
+
backgroundColor: copied
|
|
193
|
+
? "var(--kyro-success)"
|
|
194
|
+
: "var(--kyro-surface-accent)",
|
|
195
|
+
color: copied ? "white" : "var(--kyro-text-secondary)",
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
{copied ? "Copied" : "Copy"}
|
|
199
|
+
</button>
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={toggleFullScreen}
|
|
203
|
+
className="text-[10px] px-2 py-0.5 rounded font-medium bg-[var(--kyro-surface-accent)] hover:bg-[var(--kyro-border)] transition-all"
|
|
204
|
+
style={{ color: "var(--kyro-text-secondary)" }}
|
|
205
|
+
>
|
|
206
|
+
{isFullScreen ? "Exit" : "Expand"}
|
|
207
|
+
</button>
|
|
208
|
+
</div>
|
|
173
209
|
</div>
|
|
174
210
|
|
|
211
|
+
{/* Editor container */}
|
|
175
212
|
<div
|
|
176
|
-
className={`
|
|
177
|
-
|
|
178
|
-
}
|
|
213
|
+
className={`relative rounded-md overflow-hidden transition-all duration-200 ${
|
|
214
|
+
isFullScreen ? "fixed inset-4 z-50" : ""
|
|
215
|
+
}`}
|
|
216
|
+
style={{
|
|
217
|
+
backgroundColor: "var(--kyro-surface)",
|
|
218
|
+
borderColor: error ? "var(--kyro-error)" : "var(--kyro-border)",
|
|
219
|
+
borderWidth: "1px",
|
|
220
|
+
}}
|
|
179
221
|
>
|
|
222
|
+
{/* Top accent bar */}
|
|
223
|
+
<div
|
|
224
|
+
className="absolute top-0 left-0 right-0 h-0.5 transition-all duration-200"
|
|
225
|
+
style={{ backgroundColor: accent }}
|
|
226
|
+
/>
|
|
227
|
+
|
|
180
228
|
<Suspense
|
|
181
229
|
fallback={
|
|
182
|
-
<div
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
230
|
+
<div
|
|
231
|
+
className="h-[280px] flex items-center justify-center"
|
|
232
|
+
style={{ backgroundColor: "var(--kyro-surface)" }}
|
|
233
|
+
>
|
|
234
|
+
<div
|
|
235
|
+
className="w-6 h-6 rounded-full animate-spin"
|
|
236
|
+
style={{
|
|
237
|
+
borderColor: "var(--kyro-border)",
|
|
238
|
+
borderTopColor: accent,
|
|
239
|
+
borderWidth: "2px",
|
|
240
|
+
}}
|
|
241
|
+
/>
|
|
186
242
|
</div>
|
|
187
243
|
}
|
|
188
244
|
>
|
|
189
245
|
<CodeMirrorEditor
|
|
190
246
|
value={value}
|
|
191
|
-
height="
|
|
247
|
+
height={isFullScreen ? "calc(100vh - 100px)" : "280px"}
|
|
248
|
+
width="100%"
|
|
192
249
|
extensions={extensions}
|
|
193
|
-
theme={
|
|
250
|
+
theme={codeMirrorTheme}
|
|
194
251
|
onChange={handleChange}
|
|
195
252
|
editable={!disabled}
|
|
196
|
-
basicSetup={
|
|
253
|
+
basicSetup={{
|
|
254
|
+
lineNumbers: true,
|
|
255
|
+
highlightActiveLine: true,
|
|
256
|
+
bracketMatching: true,
|
|
257
|
+
closeBrackets: true,
|
|
258
|
+
autocompletion: true,
|
|
259
|
+
foldGutter: true,
|
|
260
|
+
}}
|
|
197
261
|
style={{
|
|
198
262
|
fontSize: "13px",
|
|
199
|
-
fontFamily:
|
|
200
|
-
"'Fira Code', 'JetBrains Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace",
|
|
263
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
|
201
264
|
}}
|
|
202
265
|
/>
|
|
203
266
|
</Suspense>
|
|
204
|
-
</div>
|
|
205
267
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Autocomplete
|
|
219
|
-
</span>
|
|
220
|
-
<span>
|
|
221
|
-
<kbd className="px-1.5 py-0.5 bg-[var(--kyro-surface-accent)] rounded text-[10px] font-mono">
|
|
222
|
-
Ctrl+Z
|
|
223
|
-
</kbd>{" "}
|
|
224
|
-
Undo
|
|
225
|
-
</span>
|
|
268
|
+
{/* Error message */}
|
|
269
|
+
{error && (
|
|
270
|
+
<div
|
|
271
|
+
className="absolute bottom-0 left-0 right-0 px-3 py-2 text-xs font-medium"
|
|
272
|
+
style={{
|
|
273
|
+
backgroundColor: "var(--kyro-danger-bg)",
|
|
274
|
+
color: "var(--kyro-error)",
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
|
+
{error}
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
226
280
|
</div>
|
|
227
281
|
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
282
|
+
{/* Footer */}
|
|
283
|
+
<div
|
|
284
|
+
className="flex items-center justify-between mt-2 text-[10px]"
|
|
285
|
+
style={{ color: "var(--kyro-text-muted)" }}
|
|
286
|
+
>
|
|
287
|
+
<span>{value.split("\n").length} lines</span>
|
|
288
|
+
<span style={{ color: accent }}>CodeMirror</span>
|
|
289
|
+
</div>
|
|
232
290
|
</div>
|
|
233
291
|
);
|
|
234
292
|
};
|
|
293
|
+
|
|
294
|
+
export default CodeField;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ChildBlocksTree } from "../blocks/ChildBlocksTree";
|
|
3
|
+
|
|
4
|
+
interface ColumnData {
|
|
5
|
+
id: number;
|
|
6
|
+
children: any[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ColumnsFieldProps {
|
|
10
|
+
columns?: number;
|
|
11
|
+
columnData?: ColumnData[];
|
|
12
|
+
onColumnsChange: (columns: number) => void;
|
|
13
|
+
onUpdateColumnChildren: (columnIndex: number, newChildren: any[]) => void;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ColumnsField: React.FC<ColumnsFieldProps> = ({
|
|
18
|
+
columns = 2,
|
|
19
|
+
columnData = [],
|
|
20
|
+
onColumnsChange,
|
|
21
|
+
onUpdateColumnChildren,
|
|
22
|
+
compact = false,
|
|
23
|
+
}) => {
|
|
24
|
+
const normalizedColumnData = Array.from({ length: columns }, (_, i) => ({
|
|
25
|
+
id: i,
|
|
26
|
+
children: columnData[i]?.children || [],
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
if (compact) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="space-y-2">
|
|
32
|
+
<div className="flex items-center gap-2">
|
|
33
|
+
<span className="text-xs font-medium text-[var(--kyro-text-muted)]">
|
|
34
|
+
Columns:
|
|
35
|
+
</span>
|
|
36
|
+
<div className="flex items-center gap-0.5 bg-[var(--kyro-surface-accent)] rounded-lg p-0.5">
|
|
37
|
+
{Array.from({ length: 6 }, (_, i) => i + 1).map((n) => (
|
|
38
|
+
<button
|
|
39
|
+
key={n}
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={() => onColumnsChange(n)}
|
|
42
|
+
className={`px-2.5 py-1 rounded-md text-xs font-medium transition-all ${
|
|
43
|
+
columns === n
|
|
44
|
+
? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-sm"
|
|
45
|
+
: "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
46
|
+
}`}
|
|
47
|
+
>
|
|
48
|
+
{n}
|
|
49
|
+
</button>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="overflow-x-auto pb-1">
|
|
54
|
+
<div className="flex gap-2 min-w-fit">
|
|
55
|
+
{normalizedColumnData.map((col, i) => (
|
|
56
|
+
<div
|
|
57
|
+
key={i}
|
|
58
|
+
className="w-[180px] border-2 border-dashed border-[var(--kyro-border)] rounded-lg p-2"
|
|
59
|
+
>
|
|
60
|
+
<div className="flex items-center justify-between mb-1.5 pb-1 border-b border-[var(--kyro-border)]">
|
|
61
|
+
<span className="text-[10px] font-medium text-[var(--kyro-text-muted)]">
|
|
62
|
+
Col {i + 1}
|
|
63
|
+
</span>
|
|
64
|
+
<span className="text-[10px] text-[var(--kyro-text-muted)]">
|
|
65
|
+
{col.children.length}
|
|
66
|
+
</span>
|
|
67
|
+
</div>
|
|
68
|
+
<ChildBlocksTree
|
|
69
|
+
blockId={`col-${i}`}
|
|
70
|
+
children={col.children}
|
|
71
|
+
onUpdateChildren={(c) => onUpdateColumnChildren(i, c)}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="space-y-4">
|
|
83
|
+
<div className="flex items-center gap-2">
|
|
84
|
+
<span className="text-xs font-medium text-[var(--kyro-text-muted)]">
|
|
85
|
+
Columns:
|
|
86
|
+
</span>
|
|
87
|
+
<div className="flex items-center gap-0.5 bg-[var(--kyro-surface-accent)] rounded-lg p-0.5">
|
|
88
|
+
{Array.from({ length: 6 }, (_, i) => i + 1).map((n) => (
|
|
89
|
+
<button
|
|
90
|
+
key={n}
|
|
91
|
+
type="button"
|
|
92
|
+
onClick={() => onColumnsChange(n)}
|
|
93
|
+
className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
|
|
94
|
+
columns === n
|
|
95
|
+
? "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] shadow-sm"
|
|
96
|
+
: "text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-text-primary)]"
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
{n}
|
|
100
|
+
</button>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="overflow-x-auto pb-2">
|
|
105
|
+
<div
|
|
106
|
+
className="grid gap-3 min-w-fit"
|
|
107
|
+
style={{
|
|
108
|
+
gridTemplateColumns: `repeat(${columns}, minmax(200px, 1fr))`,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
{normalizedColumnData.map((col, i) => (
|
|
112
|
+
<div
|
|
113
|
+
key={i}
|
|
114
|
+
className="border-2 border-dashed border-[var(--kyro-border)] rounded-lg p-3"
|
|
115
|
+
>
|
|
116
|
+
<div className="flex items-center justify-between mb-3 pb-2 border-b border-[var(--kyro-border)]">
|
|
117
|
+
<span className="text-xs font-medium text-[var(--kyro-text-primary)]">
|
|
118
|
+
Column {i + 1}
|
|
119
|
+
</span>
|
|
120
|
+
<span className="text-[10px] text-[var(--kyro-text-muted)]">
|
|
121
|
+
{col.children.length} blocks
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
<ChildBlocksTree
|
|
125
|
+
blockId={`col-${i}`}
|
|
126
|
+
children={col.children}
|
|
127
|
+
onUpdateChildren={(c) => onUpdateColumnChildren(i, c)}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export default ColumnsField;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { DateField as DateFieldType } from "@kyro-cms/core";
|
|
1
|
+
import type { DateField as DateFieldType } from "@kyro-cms/core/client";
|
|
3
2
|
|
|
4
3
|
interface DateFieldComponentProps {
|
|
5
4
|
field: DateFieldType;
|
|
@@ -16,26 +15,14 @@ export default function DateField({
|
|
|
16
15
|
error,
|
|
17
16
|
disabled,
|
|
18
17
|
}: DateFieldComponentProps) {
|
|
19
|
-
const [isDark, setIsDark] = useState(false);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
setIsDark(document.documentElement.classList.contains("dark"));
|
|
23
|
-
const observer = new MutationObserver(() => {
|
|
24
|
-
setIsDark(document.documentElement.classList.contains("dark"));
|
|
25
|
-
});
|
|
26
|
-
observer.observe(document.documentElement, {
|
|
27
|
-
attributes: true,
|
|
28
|
-
attributeFilter: ["class"],
|
|
29
|
-
});
|
|
30
|
-
return () => observer.disconnect();
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
18
|
return (
|
|
34
19
|
<div className="space-y-1">
|
|
35
20
|
{field.label && (
|
|
36
|
-
<label className="block text-sm font-medium">
|
|
21
|
+
<label className="block text-sm font-medium text-[var(--kyro-text-primary)]">
|
|
37
22
|
{field.label}
|
|
38
|
-
{field.required &&
|
|
23
|
+
{field.required && (
|
|
24
|
+
<span className="text-[var(--kyro-error)] ml-1">*</span>
|
|
25
|
+
)}
|
|
39
26
|
</label>
|
|
40
27
|
)}
|
|
41
28
|
<input
|
|
@@ -48,22 +35,20 @@ export default function DateField({
|
|
|
48
35
|
required={field.required}
|
|
49
36
|
className={`w-full px-3 py-2 border rounded-md text-sm transition-colors ${
|
|
50
37
|
error
|
|
51
|
-
? "border-
|
|
38
|
+
? "border-[var(--kyro-error)] focus:border-[var(--kyro-error)] focus:ring-[var(--kyro-error)]"
|
|
52
39
|
: "border-[var(--kyro-border)] focus:border-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)]"
|
|
53
40
|
} ${
|
|
54
41
|
disabled || field.admin?.readOnly
|
|
55
42
|
? "bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)] opacity-50"
|
|
56
|
-
:
|
|
57
|
-
? "bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)]"
|
|
58
|
-
: "bg-white text-gray-900"
|
|
43
|
+
: "bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)]"
|
|
59
44
|
}`}
|
|
60
45
|
/>
|
|
61
46
|
{field.admin?.description && !error && (
|
|
62
|
-
<p className="text-xs text-[var(--kyro-text-
|
|
47
|
+
<p className="text-xs text-[var(--kyro-text-muted)]">
|
|
63
48
|
{field.admin.description}
|
|
64
49
|
</p>
|
|
65
50
|
)}
|
|
66
|
-
{error && <p className="text-xs text-
|
|
51
|
+
{error && <p className="text-xs text-[var(--kyro-error)]">{error}</p>}
|
|
67
52
|
</div>
|
|
68
53
|
);
|
|
69
54
|
}
|