@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.
Files changed (71) hide show
  1. package/package.json +7 -2
  2. package/src/components/Admin.tsx +1 -1
  3. package/src/components/AutoForm.tsx +966 -337
  4. package/src/components/CreateView.tsx +1 -1
  5. package/src/components/DetailView.tsx +1 -1
  6. package/src/components/EnhancedListView.tsx +156 -52
  7. package/src/components/ListView.tsx +1 -1
  8. package/src/components/Modal.tsx +65 -8
  9. package/src/components/Sidebar.astro +2 -2
  10. package/src/components/ThemeProvider.tsx +8 -2
  11. package/src/components/blocks/AccordionBlock.tsx +20 -52
  12. package/src/components/blocks/ArrayBlock.tsx +40 -31
  13. package/src/components/blocks/BlockEditModal.tsx +170 -581
  14. package/src/components/blocks/ButtonBlock.tsx +27 -128
  15. package/src/components/blocks/CodeBlock.tsx +88 -40
  16. package/src/components/blocks/ColumnsBlock.tsx +27 -85
  17. package/src/components/blocks/FileBlock.tsx +38 -39
  18. package/src/components/blocks/HeadingBlock.tsx +9 -31
  19. package/src/components/blocks/HeroBlock.tsx +42 -100
  20. package/src/components/blocks/ImageBlock.tsx +6 -7
  21. package/src/components/blocks/LinkBlock.tsx +27 -33
  22. package/src/components/blocks/ListBlock.tsx +47 -26
  23. package/src/components/blocks/RelationshipBlock.tsx +26 -233
  24. package/src/components/blocks/RichTextBlock.tsx +66 -0
  25. package/src/components/blocks/VStackBlock.tsx +23 -37
  26. package/src/components/blocks/VideoBlock.tsx +52 -32
  27. package/src/components/fields/AccordionField.tsx +213 -0
  28. package/src/components/fields/ArrayField.tsx +241 -0
  29. package/src/components/fields/BlocksField.tsx +5 -5
  30. package/src/components/fields/ButtonField.tsx +53 -0
  31. package/src/components/fields/CheckboxField.tsx +7 -3
  32. package/src/components/fields/ChildrenField.tsx +48 -0
  33. package/src/components/fields/CodeField.tsx +154 -94
  34. package/src/components/fields/ColumnsField.tsx +137 -0
  35. package/src/components/fields/DateField.tsx +9 -24
  36. package/src/components/fields/EditorClient.tsx +426 -160
  37. package/src/components/fields/HeadingField.tsx +31 -0
  38. package/src/components/fields/HeroField.tsx +101 -0
  39. package/src/components/fields/JSONField.tsx +7 -27
  40. package/src/components/fields/LinkField.tsx +81 -0
  41. package/src/components/fields/ListField.tsx +74 -0
  42. package/src/components/fields/MarkdownField.tsx +4 -26
  43. package/src/components/fields/NumberField.tsx +9 -27
  44. package/src/components/fields/PortableTextField.tsx +61 -49
  45. package/src/components/fields/RelationshipBlockField.tsx +233 -0
  46. package/src/components/fields/RelationshipField.tsx +59 -13
  47. package/src/components/fields/SelectField.tsx +6 -4
  48. package/src/components/fields/TextField.tsx +9 -24
  49. package/src/components/fields/UploadField.tsx +613 -0
  50. package/src/components/fields/VideoField.tsx +73 -0
  51. package/src/components/fields/extensions/blockComponents.tsx +11 -1
  52. package/src/components/fields/extensions/blocksStore.ts +1 -1
  53. package/src/components/fields/index.ts +12 -1
  54. package/src/components/layout/Layout.tsx +1 -1
  55. package/src/lib/api.ts +163 -0
  56. package/src/lib/config.ts +1 -1
  57. package/src/lib/dataStore.ts +87 -30
  58. package/src/lib/date-utils.ts +69 -0
  59. package/src/lib/db/version-adapter.ts +248 -0
  60. package/src/lib/i18n.tsx +353 -0
  61. package/src/lib/slugify.ts +15 -0
  62. package/src/lib/validation.ts +250 -0
  63. package/src/pages/api/[collection]/[id]/publish.ts +12 -4
  64. package/src/pages/api/[collection]/[id]/versions.ts +39 -9
  65. package/src/pages/api/[collection]/[id].ts +13 -1
  66. package/src/pages/api/[collection]/index.ts +5 -6
  67. package/src/styles/main.css +12 -2
  68. package/src/components/blocks/BlockEditModal.MARKER +0 -12
  69. package/src/components/fields/FileField.tsx +0 -390
  70. package/src/components/fields/HybridContentField.tsx +0 -109
  71. 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, ExternalLink } from "lucide-react";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { ButtonField } from "../fields/ButtonField";
7
8
 
8
- interface ButtonBlockProps {
9
- block: any;
10
- index: number;
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-4 mb-4 relative group">
24
- <div className="flex items-center justify-between mb-3">
25
- <div className="flex items-center gap-2">
26
- <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
27
- Button
28
- </span>
29
- </div>
30
- <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
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-[-90deg]" />
35
+ <ChevronRight className="w-3 h-3 rotate-90" />
37
36
  </button>
38
- <button type="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 type="button"
45
+ <button
46
+ type="button"
46
47
  onClick={() => removeBlock(block.id)}
47
- className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
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
- <div className="space-y-3">
56
- <div>
57
- <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
58
- Button 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="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-code border border-[var(--kyro-border)] rounded-md p-4 mb-2 relative group font-mono">
22
- <div className="flex items-center justify-between mb-1">
23
- <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
24
- Code
25
- </span>
26
- <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
27
- <button type="button"
28
- onClick={() => moveBlock(block.id, "up")}
29
- className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
30
- title="Move up"
31
- >
32
- <ChevronRight className="w-3 h-3 rotate-90" />
33
- </button>
34
- <button type="button"
35
- onClick={() => removeBlock(block.id)}
36
- className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
37
- title="Remove"
38
- >
39
- <X className="w-3 h-3" />
40
- </button>
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
- <div>
44
- <select
45
- value={block.data.language || "plaintext"}
46
- onChange={(e) => handleChange("language", e.target.value)}
47
- className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm mb-3"
48
- >
49
- <option value="plaintext">Plain Text</option>
50
- <option value="javascript">JavaScript</option>
51
- <option value="typescript">TypeScript</option>
52
- <option value="python">Python</option>
53
- <option value="json">JSON</option>
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, { useState } from "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, Plus, Minus, Columns3 } from "lucide-react";
7
- import { ChildBlocksTree } from "./ChildBlocksTree";
6
+ import { ChevronRight, X, Columns3 } from "lucide-react";
7
+ import { ColumnsField } from "../fields/ColumnsField";
8
8
 
9
- interface ColumnsBlockProps {
10
- block: any;
11
- index: number;
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: currentColumnData[i]?.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 = [...currentColumnData];
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-lg mb-4 overflow-hidden">
61
- <div className="flex items-center justify-between px-4 py-2 bg-[var(--kyro-surface)] border-b border-[var(--kyro-border)]">
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-4 h-4 text-[var(--kyro-primary)]" />
64
- <span className="text-sm font-medium text-[var(--kyro-text-primary)]">
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-xs text-[var(--kyro-text-muted)]">
53
+ <span className="text-[10px] text-[var(--kyro-text-muted)]">
68
54
  ({columns})
69
55
  </span>
70
56
  </div>
71
- <div className="flex items-center gap-1 bg-[var(--kyro-surface-accent)] rounded">
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.5 hover:bg-[var(--kyro-surface-accent)] rounded"
61
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
97
62
  title="Move up"
98
63
  >
99
- <ChevronRight className="w-3.5 h-3.5 rotate-[-90deg]" />
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.5 hover:bg-[var(--kyro-surface-accent)] rounded"
69
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
105
70
  title="Move down"
106
71
  >
107
- <ChevronRight className="w-3.5 h-3.5 rotate-90" />
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.5 hover:bg-red-50 rounded"
77
+ className="p-1 hover:bg-red-50 rounded text-red-500"
113
78
  title="Remove"
114
79
  >
115
- <X className="w-3.5 h-3.5 text-red-500" />
80
+ <X className="w-3 h-3" />
116
81
  </button>
117
82
  </div>
118
83
  </div>
119
84
 
120
- <div
121
- className="grid gap-4 p-4"
122
- style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}
123
- >
124
- {currentColumnData.map((col: any, colIndex: number) => (
125
- <div
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-4 mb-2 relative group flex items-center gap-3">
22
- <div className="flex-1">
23
- <div className="flex items-center justify-between mb-1">
24
- <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
25
- File
26
- </span>
27
- <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
28
- <button type="button"
29
- onClick={() => moveBlock(block.id, "up")}
30
- className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
31
- title="Move up"
32
- >
33
- <ChevronRight className="w-3 h-3 rotate-90" />
34
- </button>
35
- <button type="button"
36
- onClick={() => removeBlock(block.id)}
37
- className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
38
- title="Remove"
39
- >
40
- <X className="w-3 h-3" />
41
- </button>
42
- </div>
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
  };