@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
|
@@ -3,16 +3,16 @@ import {
|
|
|
3
3
|
useBlockById,
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
|
-
import { ChevronRight, X
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { ButtonField } from "../fields/ButtonField";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
block
|
|
10
|
-
index
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const ButtonBlock: React.FC<ButtonBlockProps> = ({ block, index }) => {
|
|
9
|
+
export const ButtonBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
14
13
|
const blockData = useBlockById(block.id);
|
|
15
14
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
16
|
const data = blockData?.data ?? block.data ?? {};
|
|
17
17
|
|
|
18
18
|
const handleChange = (field: string, value: any) => {
|
|
@@ -20,31 +20,32 @@ export const ButtonBlock: React.FC<ButtonBlockProps> = ({ block, index }) => {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<div className="block-button border border-[var(--kyro-border)] rounded-md p-
|
|
24
|
-
<div className="flex items-center justify-between mb-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<button type="button"
|
|
23
|
+
<div className="block-button border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center justify-between mb-2">
|
|
25
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
26
|
+
Button
|
|
27
|
+
</span>
|
|
28
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
32
31
|
onClick={() => moveBlock(block.id, "up")}
|
|
33
32
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
34
33
|
title="Move up"
|
|
35
34
|
>
|
|
36
|
-
<ChevronRight className="w-3 h-3 rotate-
|
|
35
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
37
36
|
</button>
|
|
38
|
-
<button
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
39
|
onClick={() => moveBlock(block.id, "down")}
|
|
40
40
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
41
|
title="Move down"
|
|
42
42
|
>
|
|
43
43
|
<ChevronRight className="w-3 h-3" />
|
|
44
44
|
</button>
|
|
45
|
-
<button
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
46
47
|
onClick={() => removeBlock(block.id)}
|
|
47
|
-
className="p-1 hover:bg-
|
|
48
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
48
49
|
title="Remove"
|
|
49
50
|
>
|
|
50
51
|
<X className="w-3 h-3" />
|
|
@@ -52,114 +53,12 @@ export const ButtonBlock: React.FC<ButtonBlockProps> = ({ block, index }) => {
|
|
|
52
53
|
</div>
|
|
53
54
|
</div>
|
|
54
55
|
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
type="text"
|
|
62
|
-
value={block.data.text || ""}
|
|
63
|
-
onChange={(e) => handleChange("text", e.target.value)}
|
|
64
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
65
|
-
placeholder="Click here..."
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div>
|
|
70
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
71
|
-
URL
|
|
72
|
-
</label>
|
|
73
|
-
<input
|
|
74
|
-
type="url"
|
|
75
|
-
value={block.data.url || ""}
|
|
76
|
-
onChange={(e) => handleChange("url", e.target.value)}
|
|
77
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
78
|
-
placeholder="https://..."
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div className="grid grid-cols-3 gap-3">
|
|
83
|
-
<div>
|
|
84
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
85
|
-
Style
|
|
86
|
-
</label>
|
|
87
|
-
<select
|
|
88
|
-
value={block.data.variant || "primary"}
|
|
89
|
-
onChange={(e) => handleChange("variant", e.target.value)}
|
|
90
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
91
|
-
>
|
|
92
|
-
<option value="primary">Primary</option>
|
|
93
|
-
<option value="secondary">Secondary</option>
|
|
94
|
-
<option value="outline">Outline</option>
|
|
95
|
-
<option value="ghost">Ghost</option>
|
|
96
|
-
</select>
|
|
97
|
-
</div>
|
|
98
|
-
<div>
|
|
99
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
100
|
-
Size
|
|
101
|
-
</label>
|
|
102
|
-
<select
|
|
103
|
-
value={block.data.size || "md"}
|
|
104
|
-
onChange={(e) => handleChange("size", e.target.value)}
|
|
105
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
106
|
-
>
|
|
107
|
-
<option value="sm">Small</option>
|
|
108
|
-
<option value="md">Medium</option>
|
|
109
|
-
<option value="lg">Large</option>
|
|
110
|
-
</select>
|
|
111
|
-
</div>
|
|
112
|
-
<div>
|
|
113
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
114
|
-
Target
|
|
115
|
-
</label>
|
|
116
|
-
<select
|
|
117
|
-
value={block.data.target || "_self"}
|
|
118
|
-
onChange={(e) => handleChange("target", e.target.value)}
|
|
119
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
120
|
-
>
|
|
121
|
-
<option value="_self">Same Tab</option>
|
|
122
|
-
<option value="_blank">New Tab</option>
|
|
123
|
-
</select>
|
|
124
|
-
</div>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<div>
|
|
128
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
129
|
-
Alignment
|
|
130
|
-
</label>
|
|
131
|
-
<select
|
|
132
|
-
value={block.data.align || "left"}
|
|
133
|
-
onChange={(e) => handleChange("align", e.target.value)}
|
|
134
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
135
|
-
>
|
|
136
|
-
<option value="left">Left</option>
|
|
137
|
-
<option value="center">Center</option>
|
|
138
|
-
<option value="right">Right</option>
|
|
139
|
-
</select>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
{/* Preview */}
|
|
144
|
-
<div className="mt-4 p-4 bg-[var(--kyro-surface)] rounded border border-[var(--kyro-border)]">
|
|
145
|
-
<p className="text-xs text-[var(--kyro-text-muted)] mb-2">Preview:</p>
|
|
146
|
-
<button type="button"
|
|
147
|
-
className={`px-4 py-2 rounded font-medium text-sm
|
|
148
|
-
${block.data.variant === "primary" ? "bg-[var(--kyro-primary)] text-white" : ""}
|
|
149
|
-
${block.data.variant === "secondary" ? "bg-[var(--kyro-secondary)] text-white" : ""}
|
|
150
|
-
${block.data.variant === "outline" ? "border border-[var(--kyro-primary)] text-[var(--kyro-primary)] bg-transparent" : ""}
|
|
151
|
-
${block.data.variant === "ghost" ? "text-[var(--kyro-primary)] bg-transparent" : ""}
|
|
152
|
-
${block.data.size === "sm" ? "text-xs px-3 py-1" : ""}
|
|
153
|
-
${block.data.size === "md" ? "text-sm px-4 py-2" : ""}
|
|
154
|
-
${block.data.size === "lg" ? "text-base px-5 py-3" : ""}
|
|
155
|
-
`}
|
|
156
|
-
>
|
|
157
|
-
{block.data.text || "Button"}
|
|
158
|
-
{block.data.target === "_blank" && (
|
|
159
|
-
<ExternalLink className="w-3 h-3 ml-2 inline" />
|
|
160
|
-
)}
|
|
161
|
-
</button>
|
|
162
|
-
</div>
|
|
56
|
+
<ButtonField
|
|
57
|
+
text={data.text || "Button"}
|
|
58
|
+
url={data.url || ""}
|
|
59
|
+
onChange={handleChange}
|
|
60
|
+
compact
|
|
61
|
+
/>
|
|
163
62
|
</div>
|
|
164
63
|
);
|
|
165
64
|
};
|
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
useBlockById,
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
|
-
import { ChevronRight, X } from "lucide-react";
|
|
6
|
+
import { ChevronRight, X, Code2 } from "lucide-react";
|
|
7
|
+
import { CodeField } from "../fields/CodeField";
|
|
7
8
|
|
|
8
9
|
export const CodeBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
10
|
block,
|
|
@@ -18,48 +19,95 @@ export const CodeBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
|
-
<div className="block
|
|
22
|
-
<div className="flex items-center justify-between mb-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
className="
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
<div className="group/block relative border border-[var(--kyro-border)] rounded-2xl p-6 mb-6 transition-all duration-300 bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)]/20">
|
|
23
|
+
<div className="flex items-center justify-between mb-6">
|
|
24
|
+
<div className="flex items-center gap-3">
|
|
25
|
+
<div className="w-9 h-9 rounded-xl bg-[var(--kyro-primary)]/10 flex items-center justify-center text-[var(--kyro-primary)] transition-transform group-hover/block:scale-110">
|
|
26
|
+
<Code2 className="w-5 h-5" />
|
|
27
|
+
</div>
|
|
28
|
+
<div>
|
|
29
|
+
<h4 className="text-sm font-bold tracking-tight text-[var(--kyro-text-primary)]">Code Snippet</h4>
|
|
30
|
+
<p className="text-[10px] font-medium text-[var(--kyro-text-muted)] uppercase tracking-widest">
|
|
31
|
+
Block Editor • {data.language || "javascript"}
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="flex items-center gap-1.5 opacity-0 group-hover/block:opacity-100 transition-all translate-x-2 group-hover/block:translate-x-0">
|
|
37
|
+
<div className="flex bg-[var(--kyro-surface-accent)]/50 p-1 rounded-xl border border-[var(--kyro-border)]">
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
41
|
+
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded-lg transition-all text-[var(--kyro-text-muted)] hover:text-[var(--kyro-primary)]"
|
|
42
|
+
title="Move up"
|
|
43
|
+
>
|
|
44
|
+
<ChevronRight className="w-4 h-4 rotate-[-90deg]" />
|
|
45
|
+
</button>
|
|
46
|
+
<div className="w-px h-4 bg-[var(--kyro-border)] mx-1 self-center" />
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
onClick={() => removeBlock(block.id)}
|
|
50
|
+
className="p-1.5 hover:bg-red-500/10 rounded-lg transition-all text-[var(--kyro-text-muted)] hover:text-red-500"
|
|
51
|
+
title="Remove"
|
|
52
|
+
>
|
|
53
|
+
<X className="w-4 h-4" />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
41
56
|
</div>
|
|
42
57
|
</div>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<option value="html">HTML</option>
|
|
55
|
-
<option value="css">CSS</option>
|
|
56
|
-
</select>
|
|
57
|
-
<textarea
|
|
58
|
-
value={block.data.code || ""}
|
|
59
|
-
onChange={(e) => handleChange("code", e.target.value)}
|
|
60
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm min-h-[120px] resize-none"
|
|
61
|
-
placeholder="Enter code..."
|
|
58
|
+
|
|
59
|
+
<div className="space-y-6">
|
|
60
|
+
<CodeField
|
|
61
|
+
field={{
|
|
62
|
+
type: "code",
|
|
63
|
+
name: "code",
|
|
64
|
+
label: "Source Code",
|
|
65
|
+
language: data.language || "javascript"
|
|
66
|
+
}}
|
|
67
|
+
value={data.code || ""}
|
|
68
|
+
onChange={(val) => handleChange("code", val)}
|
|
62
69
|
/>
|
|
70
|
+
|
|
71
|
+
<div className="flex items-center gap-4 bg-[var(--kyro-surface-accent)]/20 p-4 rounded-xl border border-[var(--kyro-border)]/50">
|
|
72
|
+
<div className="flex-1">
|
|
73
|
+
<label className="text-[10px] font-black uppercase tracking-widest text-[var(--kyro-text-muted)] mb-2 block">
|
|
74
|
+
Syntax Highlighting
|
|
75
|
+
</label>
|
|
76
|
+
<div className="relative">
|
|
77
|
+
<select
|
|
78
|
+
value={data.language || "javascript"}
|
|
79
|
+
onChange={(e) => handleChange("language", e.target.value)}
|
|
80
|
+
className="w-full pl-4 pr-10 py-2.5 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl text-xs font-bold text-[var(--kyro-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-primary)]/20 focus:border-[var(--kyro-primary)] transition-all appearance-none cursor-pointer"
|
|
81
|
+
>
|
|
82
|
+
<option value="plaintext">Plain Text</option>
|
|
83
|
+
<option value="javascript">JavaScript</option>
|
|
84
|
+
<option value="typescript">TypeScript</option>
|
|
85
|
+
<option value="python">Python</option>
|
|
86
|
+
<option value="json">JSON</option>
|
|
87
|
+
<option value="html">HTML</option>
|
|
88
|
+
<option value="css">CSS</option>
|
|
89
|
+
<option value="sql">SQL</option>
|
|
90
|
+
<option value="rust">Rust</option>
|
|
91
|
+
<option value="markdown">Markdown</option>
|
|
92
|
+
</select>
|
|
93
|
+
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-[var(--kyro-text-muted)]">
|
|
94
|
+
<ChevronRight className="w-4 h-4 rotate-90" />
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div className="hidden sm:block w-px h-10 bg-[var(--kyro-border)]" />
|
|
100
|
+
|
|
101
|
+
<div className="hidden sm:flex flex-col justify-center">
|
|
102
|
+
<span className="text-[10px] font-black uppercase tracking-widest text-[var(--kyro-text-muted)] mb-2">
|
|
103
|
+
Status
|
|
104
|
+
</span>
|
|
105
|
+
<div className="flex items-center gap-2 px-3 py-2 bg-[var(--kyro-surface)] border border-[var(--kyro-border)] rounded-xl">
|
|
106
|
+
<div className="w-2 h-2 rounded-full bg-blue-500 animate-pulse" />
|
|
107
|
+
<span className="text-[10px] font-bold text-[var(--kyro-text-primary)] tracking-wide">EDITING</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
63
111
|
</div>
|
|
64
112
|
</div>
|
|
65
113
|
);
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import {
|
|
3
3
|
useBlockById,
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
|
-
import { ChevronRight, X,
|
|
7
|
-
import {
|
|
6
|
+
import { ChevronRight, X, Columns3 } from "lucide-react";
|
|
7
|
+
import { ColumnsField } from "../fields/ColumnsField";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
block
|
|
11
|
-
index
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const ColumnsBlock: React.FC<ColumnsBlockProps> = ({ block, index }) => {
|
|
9
|
+
export const ColumnsBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
15
13
|
const blockData = useBlockById(block.id);
|
|
16
14
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
15
|
|
|
@@ -19,23 +17,11 @@ export const ColumnsBlock: React.FC<ColumnsBlockProps> = ({ block, index }) => {
|
|
|
19
17
|
const columns = data.columns || 2;
|
|
20
18
|
const columnData = data.columnData || [];
|
|
21
19
|
|
|
22
|
-
const ensureColumnData = () => {
|
|
23
|
-
if (columnData.length !== columns) {
|
|
24
|
-
return Array.from({ length: columns }, (_, i) => ({
|
|
25
|
-
id: i,
|
|
26
|
-
children: columnData[i]?.children || [],
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
29
|
-
return columnData;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const currentColumnData = ensureColumnData();
|
|
33
|
-
|
|
34
20
|
const handleColumnsChange = (newColumns: number) => {
|
|
35
21
|
if (newColumns < 1 || newColumns > 6) return;
|
|
36
22
|
const newColumnData = Array.from({ length: newColumns }, (_, i) => ({
|
|
37
23
|
id: i,
|
|
38
|
-
children:
|
|
24
|
+
children: columnData[i]?.children || [],
|
|
39
25
|
}));
|
|
40
26
|
updateBlock(block.id, {
|
|
41
27
|
data: { ...data, columns: newColumns, columnData: newColumnData },
|
|
@@ -46,7 +32,7 @@ export const ColumnsBlock: React.FC<ColumnsBlockProps> = ({ block, index }) => {
|
|
|
46
32
|
columnIndex: number,
|
|
47
33
|
newChildren: any[],
|
|
48
34
|
) => {
|
|
49
|
-
const newColumnData = [...
|
|
35
|
+
const newColumnData = [...columnData];
|
|
50
36
|
newColumnData[columnIndex] = {
|
|
51
37
|
...newColumnData[columnIndex],
|
|
52
38
|
children: newChildren,
|
|
@@ -57,95 +43,51 @@ export const ColumnsBlock: React.FC<ColumnsBlockProps> = ({ block, index }) => {
|
|
|
57
43
|
};
|
|
58
44
|
|
|
59
45
|
return (
|
|
60
|
-
<div className="block-columns border border-[var(--kyro-border)] rounded-
|
|
61
|
-
<div className="flex items-center justify-between
|
|
46
|
+
<div className="block-columns border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
47
|
+
<div className="flex items-center justify-between mb-2">
|
|
62
48
|
<div className="flex items-center gap-2">
|
|
63
|
-
<Columns3 className="w-
|
|
64
|
-
<span className="text-
|
|
49
|
+
<Columns3 className="w-3.5 h-3.5 text-[var(--kyro-primary)]" />
|
|
50
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
65
51
|
Columns
|
|
66
52
|
</span>
|
|
67
|
-
<span className="text-
|
|
53
|
+
<span className="text-[10px] text-[var(--kyro-text-muted)]">
|
|
68
54
|
({columns})
|
|
69
55
|
</span>
|
|
70
56
|
</div>
|
|
71
|
-
<div className="flex
|
|
72
|
-
<button
|
|
73
|
-
type="button"
|
|
74
|
-
onClick={() => handleColumnsChange(columns - 1)}
|
|
75
|
-
disabled={columns <= 1}
|
|
76
|
-
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded disabled:opacity-30"
|
|
77
|
-
title="Remove column"
|
|
78
|
-
>
|
|
79
|
-
<Minus className="w-3 h-3" />
|
|
80
|
-
</button>
|
|
81
|
-
<span className="px-2 text-xs font-medium">{columns}</span>
|
|
82
|
-
<button
|
|
83
|
-
type="button"
|
|
84
|
-
onClick={() => handleColumnsChange(columns + 1)}
|
|
85
|
-
disabled={columns >= 6}
|
|
86
|
-
className="p-1.5 hover:bg-[var(--kyro-surface)] rounded disabled:opacity-30"
|
|
87
|
-
title="Add column"
|
|
88
|
-
>
|
|
89
|
-
<Plus className="w-3 h-3" />
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
<div className="flex items-center gap-1">
|
|
57
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
93
58
|
<button
|
|
94
59
|
type="button"
|
|
95
60
|
onClick={() => moveBlock(block.id, "up")}
|
|
96
|
-
className="p-1
|
|
61
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
97
62
|
title="Move up"
|
|
98
63
|
>
|
|
99
|
-
<ChevronRight className="w-3
|
|
64
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
100
65
|
</button>
|
|
101
66
|
<button
|
|
102
67
|
type="button"
|
|
103
68
|
onClick={() => moveBlock(block.id, "down")}
|
|
104
|
-
className="p-1
|
|
69
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
105
70
|
title="Move down"
|
|
106
71
|
>
|
|
107
|
-
<ChevronRight className="w-3
|
|
72
|
+
<ChevronRight className="w-3 h-3" />
|
|
108
73
|
</button>
|
|
109
74
|
<button
|
|
110
75
|
type="button"
|
|
111
76
|
onClick={() => removeBlock(block.id)}
|
|
112
|
-
className="p-1
|
|
77
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
113
78
|
title="Remove"
|
|
114
79
|
>
|
|
115
|
-
<X className="w-3
|
|
80
|
+
<X className="w-3 h-3" />
|
|
116
81
|
</button>
|
|
117
82
|
</div>
|
|
118
83
|
</div>
|
|
119
84
|
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
key={colIndex}
|
|
127
|
-
className="border-2 border-dashed border-[var(--kyro-border)] rounded-lg overflow-hidden"
|
|
128
|
-
>
|
|
129
|
-
<div className="px-3 py-2 bg-[var(--kyro-surface-accent)]/30 border-b border-[var(--kyro-border)] flex items-center justify-between">
|
|
130
|
-
<span className="text-xs font-medium text-[var(--kyro-text-muted)]">
|
|
131
|
-
Column {colIndex + 1}
|
|
132
|
-
</span>
|
|
133
|
-
<span className="text-xs text-[var(--kyro-text-muted)]">
|
|
134
|
-
({col.children?.length || 0})
|
|
135
|
-
</span>
|
|
136
|
-
</div>
|
|
137
|
-
<div className="p-3 min-h-[80px]">
|
|
138
|
-
<ChildBlocksTree
|
|
139
|
-
blockId={`${block.id}-col-${colIndex}`}
|
|
140
|
-
children={col.children || []}
|
|
141
|
-
onUpdateChildren={(newChildren) =>
|
|
142
|
-
handleUpdateColumnChildren(colIndex, newChildren)
|
|
143
|
-
}
|
|
144
|
-
/>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
))}
|
|
148
|
-
</div>
|
|
85
|
+
<ColumnsField
|
|
86
|
+
columns={columns}
|
|
87
|
+
columnData={columnData}
|
|
88
|
+
onColumnsChange={handleColumnsChange}
|
|
89
|
+
onUpdateColumnChildren={handleUpdateColumnChildren}
|
|
90
|
+
/>
|
|
149
91
|
</div>
|
|
150
92
|
);
|
|
151
93
|
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { UploadField } from "../fields/UploadField";
|
|
7
8
|
|
|
8
9
|
export const FileBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
10
|
block,
|
|
@@ -11,6 +12,7 @@ export const FileBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
11
12
|
}) => {
|
|
12
13
|
const blockData = useBlockById(block.id);
|
|
13
14
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
14
16
|
const data = blockData?.data ?? block.data ?? {};
|
|
15
17
|
|
|
16
18
|
const handleChange = (field: string, value: any) => {
|
|
@@ -18,47 +20,44 @@ export const FileBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
return (
|
|
21
|
-
<div className="block-file border border-[var(--kyro-border)] rounded-md p-
|
|
22
|
-
<div className="flex-1">
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
</
|
|
23
|
+
<div className="block-file border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center justify-between mb-1">
|
|
25
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
26
|
+
File
|
|
27
|
+
</span>
|
|
28
|
+
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
32
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
33
|
+
title="Move up"
|
|
34
|
+
>
|
|
35
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
36
|
+
</button>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
40
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
|
+
title="Move down"
|
|
42
|
+
>
|
|
43
|
+
<ChevronRight className="w-3 h-3" />
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => removeBlock(block.id)}
|
|
48
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
49
|
+
title="Remove"
|
|
50
|
+
>
|
|
51
|
+
<X className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
43
53
|
</div>
|
|
44
|
-
<input
|
|
45
|
-
type="text"
|
|
46
|
-
value={block.data.filename || ""}
|
|
47
|
-
onChange={(e) => handleChange("filename", e.target.value)}
|
|
48
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
49
|
-
placeholder="File name..."
|
|
50
|
-
/>
|
|
51
|
-
<input
|
|
52
|
-
type="url"
|
|
53
|
-
value={block.data.url || ""}
|
|
54
|
-
onChange={(e) => handleChange("url", e.target.value)}
|
|
55
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm mt-2"
|
|
56
|
-
placeholder="Download URL..."
|
|
57
|
-
/>
|
|
58
|
-
</div>
|
|
59
|
-
<div className="w-6 h-6 flex items-center justify-center text-[var(--kyro-text-muted)] flex-shrink-0">
|
|
60
|
-
📄
|
|
61
54
|
</div>
|
|
55
|
+
|
|
56
|
+
<UploadField
|
|
57
|
+
field={{ label: "File", name: "file", maxCount: 1 }}
|
|
58
|
+
value={data.file}
|
|
59
|
+
onChange={(v) => handleChange("file", v)}
|
|
60
|
+
/>
|
|
62
61
|
</div>
|
|
63
62
|
);
|
|
64
63
|
};
|