@kyro-cms/admin 0.1.6 → 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.
Files changed (179) hide show
  1. package/README.md +149 -51
  2. package/package.json +54 -5
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +137 -28
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +2155 -770
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +4 -4
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +200 -58
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +890 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +192 -54
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +206 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/ThemeProvider.tsx +8 -2
  26. package/src/components/UserManagement.tsx +204 -0
  27. package/src/components/VersionHistoryPanel.tsx +3 -3
  28. package/src/components/WebhookManager.tsx +608 -0
  29. package/src/components/blocks/AccordionBlock.tsx +65 -0
  30. package/src/components/blocks/ArrayBlock.tsx +84 -0
  31. package/src/components/blocks/BlockEditModal.tsx +363 -0
  32. package/src/components/blocks/ButtonBlock.tsx +64 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +114 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +93 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +63 -0
  38. package/src/components/blocks/HeadingBlock.tsx +59 -0
  39. package/src/components/blocks/HeroBlock.tsx +99 -0
  40. package/src/components/blocks/ImageBlock.tsx +82 -0
  41. package/src/components/blocks/LinkBlock.tsx +65 -0
  42. package/src/components/blocks/ListBlock.tsx +60 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +72 -0
  45. package/src/components/blocks/RichTextBlock.tsx +66 -0
  46. package/src/components/blocks/VStackBlock.tsx +61 -0
  47. package/src/components/blocks/VideoBlock.tsx +65 -0
  48. package/src/components/blocks/index.ts +10 -0
  49. package/src/components/fields/AccordionField.tsx +213 -0
  50. package/src/components/fields/ArrayField.tsx +241 -0
  51. package/src/components/fields/BlocksField.tsx +323 -0
  52. package/src/components/fields/ButtonField.tsx +53 -0
  53. package/src/components/fields/CheckboxField.tsx +18 -8
  54. package/src/components/fields/ChildrenField.tsx +48 -0
  55. package/src/components/fields/CodeField.tsx +294 -0
  56. package/src/components/fields/ColumnsField.tsx +137 -0
  57. package/src/components/fields/DateField.tsx +24 -12
  58. package/src/components/fields/EditorClient.tsx +537 -0
  59. package/src/components/fields/HeadingField.tsx +31 -0
  60. package/src/components/fields/HeroField.tsx +101 -0
  61. package/src/components/fields/JSONField.tsx +341 -0
  62. package/src/components/fields/LinkField.tsx +81 -0
  63. package/src/components/fields/ListField.tsx +74 -0
  64. package/src/components/fields/MarkdownField.tsx +260 -0
  65. package/src/components/fields/NumberField.tsx +25 -13
  66. package/src/components/fields/PortableTextField.tsx +155 -0
  67. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  68. package/src/components/fields/RelationshipBlockField.tsx +233 -0
  69. package/src/components/fields/RelationshipField.tsx +278 -60
  70. package/src/components/fields/SelectField.tsx +28 -16
  71. package/src/components/fields/TextField.tsx +31 -15
  72. package/src/components/fields/UploadField.tsx +613 -0
  73. package/src/components/fields/VideoField.tsx +73 -0
  74. package/src/components/fields/extensions/blockComponents.tsx +247 -0
  75. package/src/components/fields/extensions/blocksStore.ts +273 -0
  76. package/src/components/fields/index.ts +24 -0
  77. package/src/components/index.ts +1 -2
  78. package/src/components/layout/Header.tsx +2 -2
  79. package/src/components/layout/Layout.tsx +3 -3
  80. package/src/components/ui/Badge.tsx +9 -4
  81. package/src/components/ui/BlockDrawer.tsx +79 -0
  82. package/src/components/ui/Button.tsx +1 -1
  83. package/src/components/ui/CommandPalette.tsx +362 -0
  84. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  85. package/src/components/ui/Dropdown.tsx +1 -1
  86. package/src/components/ui/Modal.tsx +37 -12
  87. package/src/components/ui/PromptModal.tsx +94 -0
  88. package/src/components/ui/SlidePanel.tsx +43 -16
  89. package/src/components/ui/Toast.tsx +80 -14
  90. package/src/env.d.ts +16 -0
  91. package/src/env.ts +20 -0
  92. package/src/index.ts +0 -1
  93. package/src/layouts/AdminLayout.astro +164 -170
  94. package/src/layouts/AuthLayout.astro +23 -6
  95. package/src/lib/MediaService.ts +541 -0
  96. package/src/lib/api.ts +163 -0
  97. package/src/lib/auth/sqlite-adapter.ts +319 -0
  98. package/src/lib/config.ts +23 -7
  99. package/src/lib/dataStore.ts +188 -73
  100. package/src/lib/date-utils.ts +69 -0
  101. package/src/lib/db/adapter.ts +54 -0
  102. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  103. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  104. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  105. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  106. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  107. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  108. package/src/lib/db/index.ts +449 -0
  109. package/src/lib/db/mongodb-adapter.ts +207 -0
  110. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  111. package/src/lib/db/schema/mysql-auth.ts +113 -0
  112. package/src/lib/db/schema/mysql-content.ts +20 -0
  113. package/src/lib/db/schema/postgres-auth.ts +116 -0
  114. package/src/lib/db/schema/postgres-content.ts +35 -0
  115. package/src/lib/db/schema/postgres-media.ts +52 -0
  116. package/src/lib/db/schema/postgres-settings.ts +11 -0
  117. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  118. package/src/lib/db/schema/sqlite-content.ts +20 -0
  119. package/src/lib/db/version-adapter.ts +248 -0
  120. package/src/lib/graphql/index.ts +1 -0
  121. package/src/lib/graphql/schema.ts +443 -0
  122. package/src/lib/i18n.tsx +353 -0
  123. package/src/lib/rate-limit.ts +267 -0
  124. package/src/lib/slugify.ts +15 -0
  125. package/src/lib/storage.ts +374 -0
  126. package/src/lib/store.ts +85 -0
  127. package/src/lib/validation.ts +250 -0
  128. package/src/middleware.ts +70 -11
  129. package/src/pages/[collection]/[id].astro +178 -122
  130. package/src/pages/[collection]/index.astro +24 -156
  131. package/src/pages/admin/api-explorer.astro +98 -0
  132. package/src/pages/admin/graphql-explorer.astro +40 -0
  133. package/src/pages/admin/graphql.astro +97 -0
  134. package/src/pages/admin/index.astro +200 -139
  135. package/src/pages/admin/keys.astro +8 -0
  136. package/src/pages/admin/rest-playground.astro +44 -0
  137. package/src/pages/admin/webhooks.astro +8 -0
  138. package/src/pages/api/[collection]/[id]/publish.ts +52 -0
  139. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  140. package/src/pages/api/[collection]/[id]/versions.ts +66 -0
  141. package/src/pages/api/[collection]/[id].ts +114 -159
  142. package/src/pages/api/[collection]/index.ts +150 -230
  143. package/src/pages/api/auth/[id].ts +48 -69
  144. package/src/pages/api/auth/audit-logs.ts +20 -43
  145. package/src/pages/api/auth/login.ts +159 -45
  146. package/src/pages/api/auth/logout.ts +42 -24
  147. package/src/pages/api/auth/refresh.ts +119 -0
  148. package/src/pages/api/auth/register.ts +110 -40
  149. package/src/pages/api/auth/users.ts +22 -97
  150. package/src/pages/api/collections.ts +59 -0
  151. package/src/pages/api/globals/[slug]/test.ts +172 -0
  152. package/src/pages/api/globals/[slug].ts +42 -0
  153. package/src/pages/api/graphql.ts +90 -0
  154. package/src/pages/api/health.ts +417 -40
  155. package/src/pages/api/keys/[id].ts +26 -0
  156. package/src/pages/api/keys/index.ts +75 -0
  157. package/src/pages/api/media/[id].ts +309 -0
  158. package/src/pages/api/media/folders.ts +609 -0
  159. package/src/pages/api/media/index.ts +146 -0
  160. package/src/pages/api/media/resize.ts +267 -0
  161. package/src/pages/api/search.ts +82 -0
  162. package/src/pages/api/slug-availability.ts +70 -0
  163. package/src/pages/api/storage-config.ts +20 -0
  164. package/src/pages/api/storage-status.ts +206 -0
  165. package/src/pages/api/upload.ts +334 -0
  166. package/src/pages/api/webhooks/index.ts +71 -0
  167. package/src/pages/audit/index.astro +2 -104
  168. package/src/pages/login.astro +11 -11
  169. package/src/pages/media.astro +10 -0
  170. package/src/pages/preview/[collection]/[id].astro +178 -0
  171. package/src/pages/register.astro +13 -13
  172. package/src/pages/roles/index.astro +21 -21
  173. package/src/pages/settings/[slug].astro +162 -0
  174. package/src/pages/settings/index.astro +9 -0
  175. package/src/pages/users/[id].astro +29 -21
  176. package/src/pages/users/index.astro +22 -17
  177. package/src/pages/users/new.astro +18 -17
  178. package/src/styles/main.css +563 -128
  179. package/src/components/layout/Sidebar.tsx +0 -497
@@ -0,0 +1,93 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X, Columns3 } from "lucide-react";
7
+ import { ColumnsField } from "../fields/ColumnsField";
8
+
9
+ export const ColumnsBlock: 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 columns = data.columns || 2;
18
+ const columnData = data.columnData || [];
19
+
20
+ const handleColumnsChange = (newColumns: number) => {
21
+ if (newColumns < 1 || newColumns > 6) return;
22
+ const newColumnData = Array.from({ length: newColumns }, (_, i) => ({
23
+ id: i,
24
+ children: columnData[i]?.children || [],
25
+ }));
26
+ updateBlock(block.id, {
27
+ data: { ...data, columns: newColumns, columnData: newColumnData },
28
+ });
29
+ };
30
+
31
+ const handleUpdateColumnChildren = (
32
+ columnIndex: number,
33
+ newChildren: any[],
34
+ ) => {
35
+ const newColumnData = [...columnData];
36
+ newColumnData[columnIndex] = {
37
+ ...newColumnData[columnIndex],
38
+ children: newChildren,
39
+ };
40
+ updateBlock(block.id, {
41
+ data: { ...data, columnData: newColumnData },
42
+ });
43
+ };
44
+
45
+ return (
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">
48
+ <div className="flex items-center gap-2">
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">
51
+ Columns
52
+ </span>
53
+ <span className="text-[10px] text-[var(--kyro-text-muted)]">
54
+ ({columns})
55
+ </span>
56
+ </div>
57
+ <div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
58
+ <button
59
+ type="button"
60
+ onClick={() => moveBlock(block.id, "up")}
61
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
62
+ title="Move up"
63
+ >
64
+ <ChevronRight className="w-3 h-3 rotate-90" />
65
+ </button>
66
+ <button
67
+ type="button"
68
+ onClick={() => moveBlock(block.id, "down")}
69
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
70
+ title="Move down"
71
+ >
72
+ <ChevronRight className="w-3 h-3" />
73
+ </button>
74
+ <button
75
+ type="button"
76
+ onClick={() => removeBlock(block.id)}
77
+ className="p-1 hover:bg-red-50 rounded text-red-500"
78
+ title="Remove"
79
+ >
80
+ <X className="w-3 h-3" />
81
+ </button>
82
+ </div>
83
+ </div>
84
+
85
+ <ColumnsField
86
+ columns={columns}
87
+ columnData={columnData}
88
+ onColumnsChange={handleColumnsChange}
89
+ onUpdateColumnChildren={handleUpdateColumnChildren}
90
+ />
91
+ </div>
92
+ );
93
+ };
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+
8
+ export const DividerBlock: React.FC<{ block: any; index: number }> = ({
9
+ block,
10
+ index,
11
+ }) => {
12
+ const blockData = useBlockById(block.id);
13
+ const { updateBlock, removeBlock, moveBlock } = useBlockActions();
14
+ const data = blockData?.data ?? block.data ?? {};
15
+ const handleChange = (field: string, value: any) => {
16
+ updateBlock(block.id, { data: { ...data, [field]: value } });
17
+ };
18
+
19
+ return (
20
+ <div className="my-4">
21
+ <div className="border-t border-[var(--kyro-border)]" />
22
+ <div className="flex justify-between text-[var(--kyro-text-muted)] text-xs mt-1">
23
+ <span>Divider</span>
24
+ <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
25
+ <button type="button"
26
+ onClick={() => moveBlock(block.id, "up")}
27
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
28
+ title="Move up"
29
+ >
30
+ <ChevronRight className="w-3 h-3 rotate-90" />
31
+ </button>
32
+ <button type="button"
33
+ onClick={() => removeBlock(block.id)}
34
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
35
+ title="Remove"
36
+ >
37
+ <X className="w-3 h-3" />
38
+ </button>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ );
43
+ };
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { UploadField } from "../fields/UploadField";
8
+
9
+ export const FileBlock: 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
+
18
+ const handleChange = (field: string, value: any) => {
19
+ updateBlock(block.id, { data: { ...data, [field]: value } });
20
+ };
21
+
22
+ return (
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>
53
+ </div>
54
+ </div>
55
+
56
+ <UploadField
57
+ field={{ label: "File", name: "file", maxCount: 1 }}
58
+ value={data.file}
59
+ onChange={(v) => handleChange("file", v)}
60
+ />
61
+ </div>
62
+ );
63
+ };
@@ -0,0 +1,59 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { HeadingField } from "../fields/HeadingField";
8
+
9
+ export const HeadingBlock: 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
+
18
+ const handleChange = (field: string, value: any) => {
19
+ updateBlock(block.id, { data: { ...data, [field]: value } });
20
+ };
21
+
22
+ return (
23
+ <div className="block-heading 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
+ Heading
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-[var(--kyro-surface-accent)] rounded"
49
+ title="Remove"
50
+ >
51
+ <X className="w-3 h-3" />
52
+ </button>
53
+ </div>
54
+ </div>
55
+
56
+ <HeadingField text={data.text || ""} onChange={handleChange} compact />
57
+ </div>
58
+ );
59
+ };
@@ -0,0 +1,99 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { HeroField } from "../fields/HeroField";
8
+ import { UploadField } from "../fields/UploadField";
9
+ import { ChildBlocksTree } from "./ChildBlocksTree";
10
+
11
+ export const HeroBlock: React.FC<{ block: any; index: number }> = ({
12
+ block,
13
+ index,
14
+ }) => {
15
+ const blockData = useBlockById(block.id);
16
+ const { updateBlock, removeBlock, moveBlock } = useBlockActions();
17
+
18
+ const data = blockData?.data ?? block.data ?? {};
19
+ const children = blockData?.children ?? block.children ?? [];
20
+
21
+ const handleChange = (field: string, value: any) => {
22
+ updateBlock(block.id, { data: { ...data, [field]: value } });
23
+ };
24
+
25
+ return (
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">
28
+ <div className="flex items-center gap-2">
29
+ <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
30
+ Hero Section
31
+ </span>
32
+ </div>
33
+ <div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
34
+ <button
35
+ type="button"
36
+ onClick={() => moveBlock(block.id, "up")}
37
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
38
+ title="Move up"
39
+ >
40
+ <ChevronRight className="w-3 h-3 rotate-90" />
41
+ </button>
42
+ <button
43
+ type="button"
44
+ onClick={() => moveBlock(block.id, "down")}
45
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
46
+ title="Move down"
47
+ >
48
+ <ChevronRight className="w-3 h-3" />
49
+ </button>
50
+ <button
51
+ type="button"
52
+ onClick={() => removeBlock(block.id)}
53
+ className="p-1 hover:bg-red-50 rounded text-red-500"
54
+ title="Remove"
55
+ >
56
+ <X className="w-3 h-3" />
57
+ </button>
58
+ </div>
59
+ </div>
60
+
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
+ />
70
+
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..."
83
+ />
84
+ </div>
85
+
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">
88
+ Children ({children.length})
89
+ </label>
90
+ <ChildBlocksTree
91
+ blockId={block.id}
92
+ children={children}
93
+ onUpdateChildren={(c) => updateBlock(block.id, { children: c })}
94
+ />
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ };
@@ -0,0 +1,82 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { UploadField } from "../fields/UploadField";
8
+
9
+ export const ImageBlock: React.FC<{ block: any; index: number }> = ({
10
+ block,
11
+ index,
12
+ }) => {
13
+ const blockData = useBlockById(block.id);
14
+ const { updateBlock, removeBlock, moveBlock } = useBlockActions();
15
+ const data = blockData?.data || block.data || {};
16
+
17
+ const handleChange = (field: string, value: any) => {
18
+ updateBlock(block.id, { data: { ...data, [field]: value } });
19
+ };
20
+
21
+ return (
22
+ <div className="block-image border border-[var(--kyro-border)] rounded-lg p-4 mb-4 relative group">
23
+ <div className="flex items-center justify-between mb-2">
24
+ <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
25
+ Image
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>
43
+ </div>
44
+ <div className="space-y-3">
45
+ <div>
46
+ <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
47
+ Image Asset
48
+ </label>
49
+ <UploadField
50
+ field={{ label: "Image Asset", name: "src", maxCount: 1 }}
51
+ value={data.src}
52
+ onChange={(value) => handleChange("src", value)}
53
+ />
54
+ </div>
55
+ <div>
56
+ <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
57
+ Alt Text
58
+ </label>
59
+ <input
60
+ type="text"
61
+ value={data.alt || ""}
62
+ onChange={(e) => handleChange("alt", e.target.value)}
63
+ className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
64
+ placeholder="Alternative text..."
65
+ />
66
+ </div>
67
+ <div>
68
+ <label className="text-xs font-medium text-[var(--kyro-text-muted)] mb-1 block">
69
+ Caption
70
+ </label>
71
+ <input
72
+ type="text"
73
+ value={data.caption || ""}
74
+ onChange={(e) => handleChange("caption", e.target.value)}
75
+ className="w-full px-3 py-2 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm"
76
+ placeholder="Image caption..."
77
+ />
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { LinkField } from "../fields/LinkField";
8
+
9
+ export const LinkBlock: 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
+
18
+ const handleChange = (field: string, value: any) => {
19
+ updateBlock(block.id, { data: { ...data, [field]: value } });
20
+ };
21
+
22
+ return (
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">
26
+ Link
27
+ </span>
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"
39
+ onClick={() => moveBlock(block.id, "up")}
40
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
41
+ title="Move up"
42
+ >
43
+ <ChevronRight className="w-3 h-3 rotate-90" />
44
+ </button>
45
+ <button
46
+ type="button"
47
+ onClick={() => moveBlock(block.id, "down")}
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"
57
+ title="Remove"
58
+ >
59
+ <X className="w-3 h-3" />
60
+ </button>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+ import { ListField } from "../fields/ListField";
8
+
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 } });
21
+ };
22
+
23
+ return (
24
+ <div className="block-list border border-[var(--kyro-border)] rounded-md p-3 mb-2 relative group">
25
+ <div className="flex items-center justify-between mb-1">
26
+ <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
27
+ List
28
+ </span>
29
+ <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
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>
54
+ </div>
55
+ </div>
56
+
57
+ <ListField items={listItems} onChange={handleChange} compact />
58
+ </div>
59
+ );
60
+ };
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import {
3
+ useBlockById,
4
+ useBlockActions,
5
+ } from "../fields/extensions/blocksStore";
6
+ import { ChevronRight, X } from "lucide-react";
7
+
8
+ export const ParagraphBlock: React.FC<{ block: any; index: number }> = ({
9
+ block,
10
+ index,
11
+ }) => {
12
+ const blockData = useBlockById(block.id);
13
+ const { updateBlock, removeBlock, moveBlock } = useBlockActions();
14
+
15
+ const data = blockData?.data || block.data || {};
16
+
17
+ const handleChange = (field: string, value: any) => {
18
+ updateBlock(block.id, { data: { ...data, [field]: value } });
19
+ };
20
+
21
+ return (
22
+ <div className="block-paragraph border-l-4 border-[var(--kyro-border)] pl-4 py-2 mb-2 relative group">
23
+ <div className="flex items-center justify-between mb-1">
24
+ <span className="text-xs font-semibold text-[var(--kyro-text-muted)] uppercase">
25
+ Paragraph
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={() => moveBlock(block.id, "down")}
37
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
38
+ title="Move down"
39
+ >
40
+ <ChevronRight className="w-3 h-3" />
41
+ </button>
42
+ <button type="button"
43
+ onClick={() => removeBlock(block.id)}
44
+ className="p-1 hover:bg-[var(--kyro-surface-accent)] rounded"
45
+ title="Remove"
46
+ >
47
+ <X className="w-3 h-3" />
48
+ </button>
49
+ </div>
50
+ </div>
51
+ <div>
52
+ <textarea
53
+ value={data.text || ""}
54
+ onChange={(e) => handleChange("text", e.target.value)}
55
+ className="w-full px-3 py-3 border border-[var(--kyro-border)] rounded bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)] text-sm min-h-[100px] resize-none"
56
+ placeholder="Enter paragraph text..."
57
+ />
58
+ </div>
59
+ </div>
60
+ );
61
+ };