@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
@@ -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 type="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 type="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 type="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
- <div className="space-y-2">
52
- <div>
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 { X, ChevronRight } from "lucide-react";
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
- interface HeroBlockProps {
10
- block: any;
11
- index: number;
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-lg p-4 mb-4 relative group bg-[var(--kyro-surface)]">
30
- <div className="flex items-center justify-between mb-4">
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-sm font-medium text-[var(--kyro-text-primary)]">
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-1 opacity-0 group-hover:opacity-100 transition-opacity">
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.5 hover:bg-[var(--kyro-surface-accent)] rounded"
37
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
41
38
  title="Move up"
42
39
  >
43
- <ChevronRight className="w-3.5 h-3.5 rotate-[-90deg] text-[var(--kyro-text-muted)]" />
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.5 hover:bg-[var(--kyro-surface-accent)] rounded"
45
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
49
46
  title="Move down"
50
47
  >
51
- <ChevronRight className="w-3.5 h-3.5 rotate-90 text-[var(--kyro-text-muted)]" />
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.5 hover:bg-red-50 rounded"
53
+ className="p-1 hover:bg-red-50 rounded text-red-500"
57
54
  title="Remove"
58
55
  >
59
- <X className="w-3.5 h-3.5 text-red-500" />
56
+ <X className="w-3 h-3" />
60
57
  </button>
61
58
  </div>
62
59
  </div>
63
60
 
64
- <div className="space-y-4">
65
- <div>
66
- <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
67
- Heading
68
- </label>
69
- <input
70
- type="text"
71
- value={data.heading || ""}
72
- onChange={(e) => handleChange("heading", e.target.value)}
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
- <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
80
- Subheading
81
- </label>
82
- <textarea
83
- value={data.subheading || ""}
84
- onChange={(e) => handleChange("subheading", e.target.value)}
85
- 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 min-h-[60px] resize-none"
86
- placeholder="Hero subheading..."
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="grid grid-cols-2 gap-3">
91
- <div>
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={handleUpdateChildren}
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 URL
47
+ Image Asset
47
48
  </label>
48
- <input
49
- type="url"
50
- value={data.src || ""}
51
- onChange={(e) => handleChange("src", 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://..."
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-dashed border-[var(--kyro-border)] rounded-md p-4 mb-2 relative group">
22
- <div className="flex items-center justify-between mb-1">
23
- <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
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 gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
27
- <button type="button"
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 type="button"
35
- onClick={() => removeBlock(block.id)}
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 { useBlocksStore } from "../fields/extensions/blocksStore";
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 }> = ({ block, index }) => {
6
- const { updateBlock, removeBlock, moveBlock } = useBlocksStore();
7
-
8
- const handleChange = (field: string, value: any) => {
9
- updateBlock(block.id, { data: { ...block.data, [field]: value } });
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-4 mb-2 relative group">
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">List</span>
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 type="button" onClick={() => moveBlock(block.id, "up")} className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded" title="Move up"><ChevronRight className="w-3 h-3 rotate-90" /></button>
18
- <button type="button" onClick={() => removeBlock(block.id)} className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded" title="Remove"><X className="w-3 h-3" /></button>
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
- <div className="space-y-2">
22
- <select
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
  };