@kyro-cms/admin 0.1.7 → 0.1.8
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 +5 -3
- 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
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { HeadingField } from "../fields/HeadingField";
|
|
7
8
|
|
|
8
9
|
export const HeadingBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
10
|
block,
|
|
@@ -25,21 +26,24 @@ export const HeadingBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
25
26
|
Heading
|
|
26
27
|
</span>
|
|
27
28
|
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
28
|
-
<button
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
29
31
|
onClick={() => moveBlock(block.id, "up")}
|
|
30
32
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
31
33
|
title="Move up"
|
|
32
34
|
>
|
|
33
35
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
34
36
|
</button>
|
|
35
|
-
<button
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
36
39
|
onClick={() => moveBlock(block.id, "down")}
|
|
37
40
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
38
41
|
title="Move down"
|
|
39
42
|
>
|
|
40
43
|
<ChevronRight className="w-3 h-3" />
|
|
41
44
|
</button>
|
|
42
|
-
<button
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
43
47
|
onClick={() => removeBlock(block.id)}
|
|
44
48
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
45
49
|
title="Remove"
|
|
@@ -48,34 +52,8 @@ export const HeadingBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
48
52
|
</button>
|
|
49
53
|
</div>
|
|
50
54
|
</div>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
54
|
-
Level
|
|
55
|
-
</label>
|
|
56
|
-
<select
|
|
57
|
-
value={data.level || 1}
|
|
58
|
-
onChange={(e) => handleChange("level", parseInt(e.target.value))}
|
|
59
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
60
|
-
>
|
|
61
|
-
<option value={1}>H1</option>
|
|
62
|
-
<option value={2}>H2</option>
|
|
63
|
-
<option value={3}>H3</option>
|
|
64
|
-
</select>
|
|
65
|
-
</div>
|
|
66
|
-
<div>
|
|
67
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
68
|
-
Text
|
|
69
|
-
</label>
|
|
70
|
-
<input
|
|
71
|
-
type="text"
|
|
72
|
-
value={data.text || ""}
|
|
73
|
-
onChange={(e) => handleChange("text", e.target.value)}
|
|
74
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
75
|
-
placeholder="Enter heading text..."
|
|
76
|
-
/>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
55
|
+
|
|
56
|
+
<HeadingField text={data.text || ""} onChange={handleChange} compact />
|
|
79
57
|
</div>
|
|
80
58
|
);
|
|
81
59
|
};
|
|
@@ -3,17 +3,18 @@ import {
|
|
|
3
3
|
useBlockById,
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
|
-
import {
|
|
6
|
+
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { HeroField } from "../fields/HeroField";
|
|
8
|
+
import { UploadField } from "../fields/UploadField";
|
|
7
9
|
import { ChildBlocksTree } from "./ChildBlocksTree";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
block
|
|
11
|
-
index
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const HeroBlock: React.FC<HeroBlockProps> = ({ block, index }) => {
|
|
11
|
+
export const HeroBlock: React.FC<{ block: any; index: number }> = ({
|
|
12
|
+
block,
|
|
13
|
+
index,
|
|
14
|
+
}) => {
|
|
15
15
|
const blockData = useBlockById(block.id);
|
|
16
16
|
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
17
|
+
|
|
17
18
|
const data = blockData?.data ?? block.data ?? {};
|
|
18
19
|
const children = blockData?.children ?? block.children ?? [];
|
|
19
20
|
|
|
@@ -21,134 +22,75 @@ export const HeroBlock: React.FC<HeroBlockProps> = ({ block, index }) => {
|
|
|
21
22
|
updateBlock(block.id, { data: { ...data, [field]: value } });
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
const handleUpdateChildren = (newChildren: any[]) => {
|
|
25
|
-
updateBlock(block.id, { children: newChildren });
|
|
26
|
-
};
|
|
27
|
-
|
|
28
25
|
return (
|
|
29
|
-
<div className="border border-[var(--kyro-border)] rounded-
|
|
30
|
-
<div className="flex items-center justify-between mb-
|
|
26
|
+
<div className="block-hero border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
27
|
+
<div className="flex items-center justify-between mb-2">
|
|
31
28
|
<div className="flex items-center gap-2">
|
|
32
|
-
<span className="text-
|
|
29
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
33
30
|
Hero Section
|
|
34
31
|
</span>
|
|
35
32
|
</div>
|
|
36
|
-
<div className="flex gap-
|
|
33
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
37
34
|
<button
|
|
38
35
|
type="button"
|
|
39
36
|
onClick={() => moveBlock(block.id, "up")}
|
|
40
|
-
className="p-1
|
|
37
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
41
38
|
title="Move up"
|
|
42
39
|
>
|
|
43
|
-
<ChevronRight className="w-3
|
|
40
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
44
41
|
</button>
|
|
45
42
|
<button
|
|
46
43
|
type="button"
|
|
47
44
|
onClick={() => moveBlock(block.id, "down")}
|
|
48
|
-
className="p-1
|
|
45
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
49
46
|
title="Move down"
|
|
50
47
|
>
|
|
51
|
-
<ChevronRight className="w-3
|
|
48
|
+
<ChevronRight className="w-3 h-3" />
|
|
52
49
|
</button>
|
|
53
50
|
<button
|
|
54
51
|
type="button"
|
|
55
52
|
onClick={() => removeBlock(block.id)}
|
|
56
|
-
className="p-1
|
|
53
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
57
54
|
title="Remove"
|
|
58
55
|
>
|
|
59
|
-
<X className="w-3
|
|
56
|
+
<X className="w-3 h-3" />
|
|
60
57
|
</button>
|
|
61
58
|
</div>
|
|
62
59
|
</div>
|
|
63
60
|
|
|
64
|
-
<div className="space-y-
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-lg font-bold"
|
|
74
|
-
placeholder="Hero heading..."
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
61
|
+
<div className="space-y-3">
|
|
62
|
+
<HeroField
|
|
63
|
+
heading={data.heading || ""}
|
|
64
|
+
subheading={data.subheading || ""}
|
|
65
|
+
ctaText={data.ctaText || ""}
|
|
66
|
+
ctaUrl={data.ctaUrl || ""}
|
|
67
|
+
onChange={handleChange}
|
|
68
|
+
compact
|
|
69
|
+
/>
|
|
77
70
|
|
|
78
|
-
<div>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
<div className="grid grid-cols-2 gap-2">
|
|
72
|
+
<UploadField
|
|
73
|
+
field={{ label: "Background", name: "bgImage", maxCount: 1 }}
|
|
74
|
+
value={data.bgImage}
|
|
75
|
+
onChange={(v) => handleChange("bgImage", v)}
|
|
76
|
+
/>
|
|
77
|
+
<input
|
|
78
|
+
type="url"
|
|
79
|
+
value={data.videoUrl || ""}
|
|
80
|
+
onChange={(e) => handleChange("videoUrl", e.target.value)}
|
|
81
|
+
className="w-full px-2.5 py-1.5 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-sm text-[var(--kyro-text-primary)] focus:outline-none focus:ring-1 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent font-mono text-xs"
|
|
82
|
+
placeholder="Video URL..."
|
|
87
83
|
/>
|
|
88
84
|
</div>
|
|
89
85
|
|
|
90
|
-
<div className="
|
|
91
|
-
<
|
|
92
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
93
|
-
Background Image URL
|
|
94
|
-
</label>
|
|
95
|
-
<input
|
|
96
|
-
type="url"
|
|
97
|
-
value={data.bgImage || ""}
|
|
98
|
-
onChange={(e) => handleChange("bgImage", e.target.value)}
|
|
99
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
100
|
-
placeholder="https://..."
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
<div>
|
|
104
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
105
|
-
Video URL (YouTube/Vimeo)
|
|
106
|
-
</label>
|
|
107
|
-
<input
|
|
108
|
-
type="url"
|
|
109
|
-
value={data.videoUrl || ""}
|
|
110
|
-
onChange={(e) => handleChange("videoUrl", e.target.value)}
|
|
111
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
112
|
-
placeholder="https://youtube.com/..."
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<div className="grid grid-cols-2 gap-3">
|
|
118
|
-
<div>
|
|
119
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
120
|
-
CTA Text
|
|
121
|
-
</label>
|
|
122
|
-
<input
|
|
123
|
-
type="text"
|
|
124
|
-
value={data.ctaText || ""}
|
|
125
|
-
onChange={(e) => handleChange("ctaText", e.target.value)}
|
|
126
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
127
|
-
placeholder="Button text..."
|
|
128
|
-
/>
|
|
129
|
-
</div>
|
|
130
|
-
<div>
|
|
131
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
132
|
-
CTA URL
|
|
133
|
-
</label>
|
|
134
|
-
<input
|
|
135
|
-
type="url"
|
|
136
|
-
value={data.ctaUrl || ""}
|
|
137
|
-
onChange={(e) => handleChange("ctaUrl", e.target.value)}
|
|
138
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-primary)] text-sm"
|
|
139
|
-
placeholder="https://..."
|
|
140
|
-
/>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<div className="pt-4 border-t border-[var(--kyro-border)]">
|
|
145
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-2 block">
|
|
86
|
+
<div className="pt-3 border-t border-[var(--kyro-border)]">
|
|
87
|
+
<label className="text-[10px] font-medium text-[var(--kyro-text-muted)] mb-1.5 block">
|
|
146
88
|
Children ({children.length})
|
|
147
89
|
</label>
|
|
148
90
|
<ChildBlocksTree
|
|
149
91
|
blockId={block.id}
|
|
150
92
|
children={children}
|
|
151
|
-
onUpdateChildren={
|
|
93
|
+
onUpdateChildren={(c) => updateBlock(block.id, { children: c })}
|
|
152
94
|
/>
|
|
153
95
|
</div>
|
|
154
96
|
</div>
|
|
@@ -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 ImageBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
10
|
block,
|
|
@@ -43,14 +44,12 @@ export const ImageBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
43
44
|
<div className="space-y-3">
|
|
44
45
|
<div>
|
|
45
46
|
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
46
|
-
Image
|
|
47
|
+
Image Asset
|
|
47
48
|
</label>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
value={data.src
|
|
51
|
-
onChange={(
|
|
52
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
53
|
-
placeholder="https://..."
|
|
49
|
+
<UploadField
|
|
50
|
+
field={{ label: "Image Asset", name: "src", maxCount: 1 }}
|
|
51
|
+
value={data.src}
|
|
52
|
+
onChange={(value) => handleChange("src", value)}
|
|
54
53
|
/>
|
|
55
54
|
</div>
|
|
56
55
|
<div>
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
useBlockActions,
|
|
5
5
|
} from "../fields/extensions/blocksStore";
|
|
6
6
|
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { LinkField } from "../fields/LinkField";
|
|
7
8
|
|
|
8
9
|
export const LinkBlock: React.FC<{ block: any; index: number }> = ({
|
|
9
10
|
block,
|
|
@@ -11,6 +12,7 @@ export const LinkBlock: 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,54 +20,46 @@ export const LinkBlock: React.FC<{ block: any; index: number }> = ({
|
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
return (
|
|
21
|
-
<div className="block-link border border-
|
|
22
|
-
<div className="flex items-center
|
|
23
|
-
<span className="text-
|
|
23
|
+
<div className="block-link border border-[var(--kyro-border)] rounded-md p-2.5 mb-2 relative group">
|
|
24
|
+
<div className="flex items-center gap-2">
|
|
25
|
+
<span className="text-[10px] font-bold text-[var(--kyro-text-muted)] uppercase shrink-0 w-8">
|
|
24
26
|
Link
|
|
25
27
|
</span>
|
|
26
|
-
<div className="flex
|
|
27
|
-
<
|
|
28
|
+
<div className="flex-1 min-w-0">
|
|
29
|
+
<LinkField
|
|
30
|
+
text={data.text || ""}
|
|
31
|
+
url={data.url || ""}
|
|
32
|
+
onChange={handleChange}
|
|
33
|
+
compact
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="flex gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
28
39
|
onClick={() => moveBlock(block.id, "up")}
|
|
29
40
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
30
41
|
title="Move up"
|
|
31
42
|
>
|
|
32
43
|
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
33
44
|
</button>
|
|
34
|
-
<button
|
|
35
|
-
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
36
48
|
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
49
|
+
title="Move down"
|
|
50
|
+
>
|
|
51
|
+
<ChevronRight className="w-3 h-3" />
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={() => removeBlock(block.id)}
|
|
56
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
37
57
|
title="Remove"
|
|
38
58
|
>
|
|
39
59
|
<X className="w-3 h-3" />
|
|
40
60
|
</button>
|
|
41
61
|
</div>
|
|
42
62
|
</div>
|
|
43
|
-
<div className="space-y-3">
|
|
44
|
-
<div>
|
|
45
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
46
|
-
URL
|
|
47
|
-
</label>
|
|
48
|
-
<input
|
|
49
|
-
type="url"
|
|
50
|
-
value={block.data.url || ""}
|
|
51
|
-
onChange={(e) => handleChange("url", e.target.value)}
|
|
52
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
53
|
-
placeholder="https://..."
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
<div>
|
|
57
|
-
<label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
|
|
58
|
-
Text
|
|
59
|
-
</label>
|
|
60
|
-
<input
|
|
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="Link text..."
|
|
66
|
-
/>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
63
|
</div>
|
|
70
64
|
);
|
|
71
65
|
};
|
|
@@ -1,39 +1,60 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useBlockById,
|
|
4
|
+
useBlockActions,
|
|
5
|
+
} from "../fields/extensions/blocksStore";
|
|
3
6
|
import { ChevronRight, X } from "lucide-react";
|
|
7
|
+
import { ListField } from "../fields/ListField";
|
|
4
8
|
|
|
5
|
-
export const ListBlock: React.FC<{ block: any; index: number }> = ({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
export const ListBlock: React.FC<{ block: any; index: number }> = ({
|
|
10
|
+
block,
|
|
11
|
+
index,
|
|
12
|
+
}) => {
|
|
13
|
+
const blockData = useBlockById(block.id);
|
|
14
|
+
const { updateBlock, removeBlock, moveBlock } = useBlockActions();
|
|
15
|
+
|
|
16
|
+
const data = blockData?.data || block.data || {};
|
|
17
|
+
const listItems = Array.isArray(data.items) ? data.items : [];
|
|
18
|
+
|
|
19
|
+
const handleChange = (items: string[]) => {
|
|
20
|
+
updateBlock(block.id, { data: { ...data, items } });
|
|
10
21
|
};
|
|
11
22
|
|
|
12
23
|
return (
|
|
13
|
-
<div className="block-list border border-[var(--kyro-border)] rounded-md p-
|
|
24
|
+
<div className="block-list border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
|
|
14
25
|
<div className="flex items-center justify-between mb-1">
|
|
15
|
-
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
26
|
+
<span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
|
|
27
|
+
List
|
|
28
|
+
</span>
|
|
16
29
|
<div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
17
|
-
<button
|
|
18
|
-
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={() => moveBlock(block.id, "up")}
|
|
33
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
34
|
+
title="Move up"
|
|
35
|
+
>
|
|
36
|
+
<ChevronRight className="w-3 h-3 rotate-90" />
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
onClick={() => moveBlock(block.id, "down")}
|
|
41
|
+
className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
|
|
42
|
+
title="Move down"
|
|
43
|
+
>
|
|
44
|
+
<ChevronRight className="w-3 h-3" />
|
|
45
|
+
</button>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
onClick={() => removeBlock(block.id)}
|
|
49
|
+
className="p-1 hover:bg-red-50 rounded text-red-500"
|
|
50
|
+
title="Remove"
|
|
51
|
+
>
|
|
52
|
+
<X className="w-3 h-3" />
|
|
53
|
+
</button>
|
|
19
54
|
</div>
|
|
20
55
|
</div>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
value={block.data.type || "unordered"}
|
|
24
|
-
onChange={(e) => handleChange("type", e.target.value)}
|
|
25
|
-
className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
|
|
26
|
-
>
|
|
27
|
-
<option value="unordered">Unordered</option>
|
|
28
|
-
<option value="ordered">Ordered</option>
|
|
29
|
-
</select>
|
|
30
|
-
<textarea
|
|
31
|
-
value={block.data.items || ""}
|
|
32
|
-
onChange={(e) => handleChange("items", e.target.value)}
|
|
33
|
-
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-[80px] resize-none"
|
|
34
|
-
placeholder="Enter list items (one per line)..."
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
56
|
+
|
|
57
|
+
<ListField items={listItems} onChange={handleChange} compact />
|
|
37
58
|
</div>
|
|
38
59
|
);
|
|
39
60
|
};
|