@kyro-cms/admin 0.3.2 → 0.3.4

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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,7 +1,8 @@
1
1
  import React, { useState, useEffect, useCallback, useRef } from "react";
2
- import { useBlocksStore, createNewBlock } from "./extensions/blocksStore";
2
+ import { useStore } from "zustand";
3
+ import { BlocksContext, createBlocksStore, createNewBlock, type BlocksStoreApi } from "./extensions/blocksStore";
3
4
  import { BlockDrawer, DraggableBlockType } from "../ui/BlockDrawer";
4
- import { Plus, Box } from "lucide-react";
5
+ import { Plus, Box } from "../ui/icons";
5
6
  import {
6
7
  BLOCK_COMPONENTS,
7
8
  getBlockComponent,
@@ -27,9 +28,9 @@ import {
27
28
  import { CSS } from "@dnd-kit/utilities";
28
29
 
29
30
  interface BlocksFieldProps {
30
- field: any;
31
- value: any;
32
- onChange?: (value: any) => void;
31
+ field: Record<string, unknown>;
32
+ value: unknown[];
33
+ onChange?: (value: unknown[]) => void;
33
34
  onBlocksChange?: () => void;
34
35
  error?: string;
35
36
  disabled?: boolean;
@@ -37,14 +38,14 @@ interface BlocksFieldProps {
37
38
  justSaved?: boolean;
38
39
  }
39
40
 
40
- import { GripVertical } from "lucide-react";
41
+ import { GripVertical } from "../ui/icons";
41
42
 
42
43
  // Sortable block wrapper for drag-and-drop
43
44
  const SortableBlockComponent = ({
44
45
  block,
45
46
  index,
46
47
  }: {
47
- block: any;
48
+ block: Record<string, unknown>;
48
49
  index: number;
49
50
  }) => {
50
51
  const {
@@ -98,44 +99,53 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
98
99
  justSaved,
99
100
  }) => {
100
101
  const [isDrawerOpen, setIsDrawerOpen] = useState(false);
101
- const { blocks, setBlocks, addBlock, setOnBlocksChange } = useBlocksStore();
102
+ const storeRef = useRef<BlocksStoreApi | null>(null);
103
+ if (!storeRef.current) {
104
+ storeRef.current = createBlocksStore();
105
+ }
106
+ const store = storeRef.current;
107
+
108
+ const blocks = useStore(store, (s) => s.blocks);
102
109
  const [activeDrag, setActiveDrag] = useState<Active | null>(null);
103
110
 
104
111
  // Register blocks change callback
105
112
  useEffect(() => {
106
113
  if (onBlocksChange) {
107
- setOnBlocksChange(onBlocksChange);
114
+ store.getState().setOnBlocksChange(onBlocksChange);
108
115
  }
109
116
  return () => {
110
- setOnBlocksChange(() => {});
117
+ store.getState().setOnBlocksChange(() => { });
111
118
  };
112
- }, [onBlocksChange, setOnBlocksChange]);
119
+ }, [onBlocksChange, store]);
113
120
 
114
121
  // Sync external value changes (e.g., auto-save restore) to store
122
+ // Track last-synced value so we don't revert our own internal mutations
123
+ const lastValueRef = useRef<unknown[] | null>(null);
115
124
  useEffect(() => {
116
- const currentIds = blocks.map((b) => b.id).join(",");
117
- const valueIds = (value || []).map((b: any) => b.id).join(",");
125
+ const valueArray = Array.isArray(value) ? value : [];
126
+ const lastValueArray = lastValueRef.current || [];
127
+ const valueIds = valueArray.map((b: Record<string, unknown>) => b.id).join(",");
128
+ const lastValueIds = lastValueArray.map((b: Record<string, unknown>) => b.id).join(",");
118
129
 
119
- // Only update if the IDs don't match (external change)
120
- if (valueIds !== currentIds) {
121
- if (value && Array.isArray(value) && value.length > 0) {
122
- setBlocks(value);
123
- } else if (!value || (Array.isArray(value) && value.length === 0)) {
124
- setBlocks([]);
125
- }
130
+ if (valueIds !== lastValueIds) {
131
+ console.log("BlocksField sync: value=", value, "valueIds=", valueIds, "lastValueIds=", lastValueIds);
132
+ store.getState().setBlocks(valueArray);
133
+ lastValueRef.current = [...valueArray];
126
134
  }
127
- }, [value]);
135
+ }, [value, field.name, store]);
128
136
 
129
137
  // Debounced sync of store changes back to parent form to reduce re-renders
130
138
  const onChangeTimer = useRef<number | null>(null);
139
+ const onChangeRef = useRef(onChange);
140
+ onChangeRef.current = onChange;
131
141
  useEffect(() => {
132
- if (!onChange) return;
142
+ if (!onChangeRef.current) return;
133
143
  if (onChangeTimer.current) {
134
144
  window.clearTimeout(onChangeTimer.current);
135
145
  onChangeTimer.current = null;
136
146
  }
137
147
  onChangeTimer.current = window.setTimeout(() => {
138
- onChange(blocks);
148
+ onChangeRef.current?.(blocks);
139
149
  }, 250);
140
150
  return () => {
141
151
  if (onChangeTimer.current) {
@@ -143,7 +153,7 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
143
153
  onChangeTimer.current = null;
144
154
  }
145
155
  };
146
- }, [blocks, onChange]);
156
+ }, [blocks]);
147
157
 
148
158
  // Determine left border style based on document status
149
159
  const getBorderClass = () => {
@@ -162,9 +172,9 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
162
172
 
163
173
  const handleAddBlock = useCallback(
164
174
  (blockType: string) => {
165
- addBlock(blockType);
175
+ store.getState().addBlock(blockType);
166
176
  },
167
- [addBlock],
177
+ [store],
168
178
  );
169
179
 
170
180
  // Set up dnd-kit sensors
@@ -196,15 +206,14 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
196
206
  const containerId = over.id.toString().replace("container-", "");
197
207
  const container = blocks.find((b) => b.id === containerId);
198
208
  if (container) {
199
- const { updateBlock } = useBlocksStore.getState();
200
209
  const newBlock = createNewBlock(blockType);
201
- updateBlock(containerId, {
210
+ store.getState().updateBlock(containerId, {
202
211
  children: [...(container.children || []), newBlock],
203
212
  });
204
213
  }
205
214
  } else {
206
215
  // Dropped on root level - add as top-level block
207
- addBlock(blockType);
216
+ handleAddBlock(blockType);
208
217
  }
209
218
  return;
210
219
  }
@@ -218,7 +227,7 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
218
227
  const newBlocks = [...blocks];
219
228
  const [movedBlock] = newBlocks.splice(oldIndex, 1);
220
229
  newBlocks.splice(newIndex, 0, movedBlock);
221
- setBlocks(newBlocks);
230
+ store.getState().setBlocks(newBlocks);
222
231
  }
223
232
  }
224
233
  };
@@ -226,98 +235,100 @@ export const BlocksField: React.FC<BlocksFieldProps> = ({
226
235
  // Render active drag overlay
227
236
  const activeBlock = activeDrag
228
237
  ? blockCategories
229
- .flatMap((cat) => cat.blocks)
230
- .find((b) => `drawer-${b.type}` === activeDrag.id) ||
231
- blocks.find((b) => b.id === activeDrag.id)
238
+ .flatMap((cat) => cat.blocks)
239
+ .find((b) => `drawer-${b.type}` === activeDrag.id) ||
240
+ blocks.find((b) => b.id === activeDrag.id)
232
241
  : null;
233
242
 
234
- const activeBlockLabel = activeBlock
235
- ? "label" in activeBlock
236
- ? (activeBlock as any).label
237
- : activeBlock.type
238
- : "Block";
243
+ const activeBlockLabel = activeBlock
244
+ ? "label" in activeBlock
245
+ ? (activeBlock as Record<string, unknown>).label
246
+ : activeBlock.type
247
+ : "Block";
239
248
 
240
249
  const borderClass = getBorderClass();
241
250
 
242
251
  return (
243
- <div className={`kyro-form-field ${borderClass}`}>
244
- <DndContext
245
- sensors={sensors}
246
- collisionDetection={closestCenter}
247
- onDragStart={handleDragStart}
248
- onDragEnd={handleDragEnd}
249
- >
250
- {/* Block Builder Toolbar */}
251
- <div className="flex items-center justify-between mb-2">
252
- <label className="kyro-form-label">{field.label || field.name}</label>
253
- <button
254
- type="button"
255
- onClick={() => setIsDrawerOpen(true)}
256
- disabled={disabled}
257
- className="flex items-center gap-2 px-3 py-2 text-sm text-[var(--kyro-primary)] hover:bg-[var(--kyro-surface-accent)]/30 rounded-md transition-colors disabled:opacity-50"
258
- >
259
- <Plus className="w-4 h-4" />
260
- Add Block
261
- </button>
262
- <BlockDrawer
263
- open={isDrawerOpen}
264
- onClose={() => setIsDrawerOpen(false)}
265
- onSelect={handleAddBlock}
252
+ <BlocksContext.Provider value={store}>
253
+ <div className={`kyro-form-field ${borderClass}`}>
254
+ <DndContext
255
+ sensors={sensors}
256
+ collisionDetection={closestCenter}
257
+ onDragStart={handleDragStart}
258
+ onDragEnd={handleDragEnd}
259
+ >
260
+ {/* Block Builder Toolbar */}
261
+ <div className="flex items-center justify-between mb-2">
262
+ <label className="kyro-form-label">{field.label || field.name}</label>
263
+ <button
264
+ type="button"
265
+ onClick={() => setIsDrawerOpen(true)}
266
+ disabled={disabled}
267
+ className="flex items-center gap-2 px-3 py-2 text-sm text-[var(--kyro-primary)] hover:bg-[var(--kyro-surface-accent)]/30 rounded-md transition-colors disabled:opacity-50 font-semibold"
268
+ >
269
+ <Plus className="w-4 h-4" />
270
+ Add Block
271
+ </button>
272
+ <BlockDrawer
273
+ open={isDrawerOpen}
274
+ onClose={() => setIsDrawerOpen(false)}
275
+ onSelect={handleAddBlock}
276
+ >
277
+ <div className="space-y-4">
278
+ {blockCategories.map((category) => (
279
+ <div key={category.title}>
280
+ <h3 className="text-xs font-semibold text-[var(--kyro-text-muted)] mb-2 tracking-wider">
281
+ {category.title}
282
+ </h3>
283
+ <div className="grid grid-cols-3 gap-2">
284
+ {category.blocks.map((block) => (
285
+ <DraggableBlockType
286
+ key={block.type}
287
+ block={block}
288
+ onSelect={handleAddBlock}
289
+ >
290
+ <div className="w-6 h-6 flex items-center justify-center rounded group-hover:bg-[var(--kyro-primary)]/10 group-hover:text-[var(--kyro-primary)] transition-all duration-300">
291
+ <span className="text-[var(--kyro-text-muted)]">
292
+ {blockIcons[
293
+ block.icon as keyof typeof blockIcons
294
+ ] || <Box className="w-4 h-4" />}
295
+ </span>
296
+ </div>
297
+ </DraggableBlockType>
298
+ ))}
299
+ </div>
300
+ </div>
301
+ ))}
302
+ </div>
303
+ </BlockDrawer>
304
+ </div>
305
+
306
+ {/* Block List with Drag-and-Drop */}
307
+ <SortableContext
308
+ items={blocks.map((b) => b.id)}
309
+ strategy={verticalListSortingStrategy}
266
310
  >
267
311
  <div className="space-y-4">
268
- {blockCategories.map((category) => (
269
- <div key={category.title}>
270
- <h3 className="text-xs font-semibold text-[var(--kyro-text-muted)] mb-2 uppercase tracking-wider">
271
- {category.title}
272
- </h3>
273
- <div className="grid grid-cols-3 gap-2">
274
- {category.blocks.map((block) => (
275
- <DraggableBlockType
276
- key={block.type}
277
- block={block}
278
- onSelect={handleAddBlock}
279
- >
280
- <div className="w-6 h-6 flex items-center justify-center rounded group-hover:bg-[var(--kyro-primary)]/10 group-hover:text-[var(--kyro-primary)] transition-all duration-300">
281
- <span className="text-[var(--kyro-text-muted)]">
282
- {blockIcons[
283
- block.icon as keyof typeof blockIcons
284
- ] || <Box className="w-4 h-4" />}
285
- </span>
286
- </div>
287
- </DraggableBlockType>
288
- ))}
289
- </div>
290
- </div>
312
+ {blocks.map((block, index) => (
313
+ <SortableBlock key={block.id || index} block={block} index={index} />
291
314
  ))}
315
+ {blocks.length === 0 && (
316
+ <div className="text-center py-12 text-[var(--kyro-text-muted)] border-2 border-dashed border-[var(--kyro-border)] rounded-lg">
317
+ Click the button above to add your first block
318
+ </div>
319
+ )}
292
320
  </div>
293
- </BlockDrawer>
294
- </div>
295
-
296
- {/* Block List with Drag-and-Drop */}
297
- <SortableContext
298
- items={blocks.map((b) => b.id)}
299
- strategy={verticalListSortingStrategy}
300
- >
301
- <div className="space-y-4">
302
- {blocks.map((block, index) => (
303
- <SortableBlock key={block.id} block={block} index={index} />
304
- ))}
305
- {blocks.length === 0 && (
306
- <div className="text-center py-12 text-[var(--kyro-text-muted)] border-2 border-dashed border-[var(--kyro-border)] rounded-lg">
307
- Click the button above to add your first block
321
+ </SortableContext>
322
+ <DragOverlay>
323
+ {activeDrag && activeBlock && (
324
+ <div className="bg-[var(--kyro-surface)] border border-[var(--kyro-primary)] rounded-md p-3 shadow-lg">
325
+ {(activeBlock as Record<string, unknown>).label || activeBlock.type || "Block"}
308
326
  </div>
309
327
  )}
310
- </div>
311
- </SortableContext>
312
- <DragOverlay>
313
- {activeDrag && activeBlock && (
314
- <div className="bg-[var(--kyro-surface)] border border-[var(--kyro-primary)] rounded-md p-3 shadow-lg">
315
- {(activeBlock as any).label || activeBlock.type || "Block"}
316
- </div>
317
- )}
318
- </DragOverlay>
319
- </DndContext>
320
- {error && <p className="kyro-form-error">{error}</p>}
321
- </div>
328
+ </DragOverlay>
329
+ </DndContext>
330
+ {error && <p className="kyro-form-error">{error}</p>}
331
+ </div>
332
+ </BlocksContext.Provider>
322
333
  );
323
334
  };
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { ExternalLink } from "lucide-react";
2
+ import { ExternalLink } from "../ui/icons";
3
3
 
4
4
  interface ButtonFieldProps {
5
5
  text?: string;
@@ -1,4 +1,5 @@
1
1
  import type { CheckboxField as CheckboxFieldType } from "@kyro-cms/core/client";
2
+ import FieldLayout from "./FieldLayout";
2
3
 
3
4
  interface CheckboxFieldComponentProps {
4
5
  field: CheckboxFieldType;
@@ -15,33 +16,31 @@ export default function CheckboxField({
15
16
  error,
16
17
  disabled,
17
18
  }: CheckboxFieldComponentProps) {
19
+ const isReadOnly = field.admin?.readOnly;
20
+
18
21
  return (
19
- <div className="space-y-1">
20
- <label className="flex items-center gap-2 cursor-pointer">
22
+ <FieldLayout
23
+ field={field}
24
+ error={error}
25
+ hideLabel={true}
26
+ >
27
+ <label className="flex items-center gap-2.5 cursor-pointer group py-0.5">
21
28
  <input
22
29
  type="checkbox"
23
30
  checked={value}
24
31
  onChange={(e) => onChange?.(e.target.checked)}
25
- disabled={disabled || field.admin?.readOnly}
26
- className={`w-4 h-4 rounded border-[var(--kyro-border)] text-[var(--kyro-sidebar-active)] focus:ring-[var(--kyro-sidebar-active)] ${
27
- disabled || field.admin?.readOnly ? "opacity-50" : ""
32
+ disabled={disabled || isReadOnly}
33
+ className={`w-4 h-4 rounded border-[var(--kyro-border)] text-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)] transition-all ${
34
+ disabled || isReadOnly ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
28
35
  }`}
29
36
  />
30
- <span className="text-sm font-medium text-[var(--kyro-text-primary)]">
37
+ <span className="text-sm font-semibold text-[var(--kyro-text-primary)] tracking-tight group-hover:text-[var(--kyro-primary)] transition-colors">
31
38
  {field.label || field.name}
32
39
  {field.required && (
33
40
  <span className="text-[var(--kyro-error)] ml-1">*</span>
34
41
  )}
35
42
  </span>
36
43
  </label>
37
- {field.admin?.description && !error && (
38
- <p className="text-xs text-[var(--kyro-text-secondary)] ml-6">
39
- {field.admin.description}
40
- </p>
41
- )}
42
- {error && (
43
- <p className="text-xs text-[var(--kyro-error)] ml-6">{error}</p>
44
- )}
45
- </div>
44
+ </FieldLayout>
46
45
  );
47
46
  }
@@ -3,8 +3,8 @@ import { ChildBlocksTree } from "../blocks/ChildBlocksTree";
3
3
 
4
4
  interface ChildrenFieldProps {
5
5
  blockId: string;
6
- children: any[];
7
- onUpdateChildren: (newChildren: any[]) => void;
6
+ children: Record<string, unknown>[];
7
+ onUpdateChildren: (newChildren: Record<string, unknown>[]) => void;
8
8
  label?: string;
9
9
  compact?: boolean;
10
10
  }
@@ -30,7 +30,7 @@ const LANGUAGES = [
30
30
  { value: "markdown", label: "Markdown" },
31
31
  ];
32
32
 
33
- const languageExtensions: Record<string, () => Promise<any>> = {
33
+ const languageExtensions: Record<string, () => Promise<unknown>> = {
34
34
  javascript: () =>
35
35
  import("@codemirror/lang-javascript").then((m) =>
36
36
  m.javascript({ jsx: true, typescript: true }),
@@ -74,7 +74,7 @@ export const CodeField: React.FC<CodeFieldProps> = ({
74
74
  }) => {
75
75
  const [isMounted, setIsMounted] = useState(false);
76
76
  const [isDark, setIsDark] = useState(false);
77
- const [extensions, setExtensions] = useState<any[]>([]);
77
+ const [extensions, setExtensions] = useState<unknown[]>([]);
78
78
  const [loading, setLoading] = useState(false);
79
79
  const [copied, setCopied] = useState(false);
80
80
  const [isFullScreen, setIsFullScreen] = useState(false);
@@ -243,7 +243,7 @@ export const CodeField: React.FC<CodeFieldProps> = ({
243
243
  }
244
244
  >
245
245
  <CodeMirrorEditor
246
- value={value}
246
+ value={value == null ? "" : value}
247
247
  height={isFullScreen ? "calc(100vh - 100px)" : "280px"}
248
248
  width="100%"
249
249
  extensions={extensions}
@@ -3,14 +3,14 @@ import { ChildBlocksTree } from "../blocks/ChildBlocksTree";
3
3
 
4
4
  interface ColumnData {
5
5
  id: number;
6
- children: any[];
6
+ children: Record<string, unknown>[];
7
7
  }
8
8
 
9
9
  interface ColumnsFieldProps {
10
10
  columns?: number;
11
11
  columnData?: ColumnData[];
12
12
  onColumnsChange: (columns: number) => void;
13
- onUpdateColumnChildren: (columnIndex: number, newChildren: any[]) => void;
13
+ onUpdateColumnChildren: (columnIndex: number, newChildren: Record<string, unknown>[]) => void;
14
14
  compact?: boolean;
15
15
  }
16
16
 
@@ -1,4 +1,5 @@
1
1
  import type { DateField as DateFieldType } from "@kyro-cms/core/client";
2
+ import FieldLayout from "./FieldLayout";
2
3
 
3
4
  interface DateFieldComponentProps {
4
5
  field: DateFieldType;
@@ -15,40 +16,26 @@ export default function DateField({
15
16
  error,
16
17
  disabled,
17
18
  }: DateFieldComponentProps) {
19
+ const isReadOnly = field.admin?.readOnly;
20
+
18
21
  return (
19
- <div className="space-y-1">
20
- {field.label && (
21
- <label className="block text-sm font-medium text-[var(--kyro-text-primary)]">
22
- {field.label}
23
- {field.required && (
24
- <span className="text-[var(--kyro-error)] ml-1">*</span>
25
- )}
26
- </label>
27
- )}
22
+ <FieldLayout
23
+ field={field}
24
+ error={error}
25
+ >
28
26
  <input
29
27
  type={field.time ? "datetime-local" : "date"}
30
- value={value}
28
+ id={field.name}
29
+ value={value == null ? "" : value}
31
30
  onChange={(e) => onChange?.(e.target.value)}
32
- disabled={disabled || field.admin?.readOnly}
31
+ disabled={disabled || isReadOnly}
33
32
  min={field.minDate}
34
33
  max={field.maxDate}
35
34
  required={field.required}
36
- className={`w-full px-3 py-2 border rounded-md text-sm transition-colors ${
37
- error
38
- ? "border-[var(--kyro-error)] focus:border-[var(--kyro-error)] focus:ring-[var(--kyro-error)]"
39
- : "border-[var(--kyro-border)] focus:border-[var(--kyro-primary)] focus:ring-[var(--kyro-primary)]"
40
- } ${
41
- disabled || field.admin?.readOnly
42
- ? "bg-[var(--kyro-bg-secondary)] text-[var(--kyro-text-secondary)] opacity-50"
43
- : "bg-[var(--kyro-surface)] text-[var(--kyro-text-primary)]"
35
+ className={`kyro-form-input ${
36
+ disabled || isReadOnly ? "opacity-50 cursor-not-allowed" : ""
44
37
  }`}
45
38
  />
46
- {field.admin?.description && !error && (
47
- <p className="text-xs text-[var(--kyro-text-muted)]">
48
- {field.admin.description}
49
- </p>
50
- )}
51
- {error && <p className="text-xs text-[var(--kyro-error)]">{error}</p>}
52
- </div>
39
+ </FieldLayout>
53
40
  );
54
41
  }
@@ -40,24 +40,24 @@ import {
40
40
  ChevronDown,
41
41
  X,
42
42
  ExternalLink,
43
- } from "lucide-react";
43
+ } from "../ui/icons";
44
44
 
45
45
  interface EditorClientProps {
46
- initialValue: any[];
47
- onChange: (blocks: any[]) => void;
46
+ initialValue: Record<string, unknown>[];
47
+ onChange: (blocks: Record<string, unknown>[]) => void;
48
48
  disabled?: boolean;
49
49
  }
50
50
 
51
- function sanitizeInitialValue(value: any): any[] {
51
+ function sanitizeInitialValue(value: unknown): Record<string, unknown>[] {
52
52
  if (!value || !Array.isArray(value)) return [];
53
- return value.filter((block) => {
53
+ return (value as Record<string, unknown>[]).filter((block) => {
54
54
  if (!block || typeof block !== "object") return false;
55
- if (!block._type) return false;
56
- if (block._type === "block" && Array.isArray(block.children)) {
57
- block.children = block.children.map((child: any) => ({
55
+ if (!("_type" in block)) return false;
56
+ if ((block as { _type?: string })._type === "block" && Array.isArray((block as { children?: unknown[] }).children)) {
57
+ (block as { children: Record<string, unknown>[] }).children = (block.children as Record<string, unknown>[]).map((child) => ({
58
58
  ...child,
59
- _type: child._type || "span",
60
- text: typeof child.text === "string" ? child.text : "",
59
+ _type: (child as { _type?: string })._type || "span",
60
+ text: typeof (child as { text?: unknown }).text === "string" ? child.text : "",
61
61
  }));
62
62
  }
63
63
  return true;
@@ -333,7 +333,7 @@ const LinkDialog: React.FC = () => {
333
333
  if (href.trim()) {
334
334
  popover.send({
335
335
  type: "edit",
336
- at: [] as any,
336
+ at: [] as string[],
337
337
  props: { href: href.trim() },
338
338
  });
339
339
  }
@@ -414,7 +414,7 @@ const StyleSelector: React.FC = () => {
414
414
  <select
415
415
  value={activeStyle}
416
416
  onChange={(e) => {
417
- send({ type: "toggle", style: e.target.value as any });
417
+ send({ type: "toggle", style: e.target.value as string });
418
418
  editor.send({ type: "focus" });
419
419
  }}
420
420
  className="appearance-none bg-transparent text-sm pr-6 pl-2 py-1 rounded hover:bg-[var(--kyro-surface-accent)] cursor-pointer focus:outline-none focus:ring-1 focus:ring-[var(--kyro-primary)]"
@@ -469,7 +469,7 @@ const Toolbar: React.FC = () => {
469
469
  };
470
470
 
471
471
  const EditorInner: React.FC<{
472
- onChange: (blocks: any[]) => void;
472
+ onChange: (blocks: Record<string, unknown>[]) => void;
473
473
  disabled?: boolean;
474
474
  }> = ({ onChange, disabled }) => {
475
475
  return (
@@ -506,32 +506,30 @@ export const EditorClient: React.FC<EditorClientProps> = ({
506
506
  }
507
507
  }, [initialValue]);
508
508
 
509
- const handleChange = useCallback(
510
- (newValue: any) => {
511
- setValue(newValue);
512
- onChange(newValue);
513
- },
514
- [onChange],
515
- );
509
+ const handleChange = useCallback(
510
+ (newValue: Record<string, unknown>[]) => {
511
+ setValue(newValue);
512
+ onChange(newValue);
513
+ },
514
+ [onChange],
515
+ );
516
516
 
517
517
  return (
518
518
  <EditorProvider
519
519
  key={JSON.stringify(value)}
520
520
  initialConfig={{
521
- schemaDefinition: schemaDefinition as any,
521
+ schemaDefinition: schemaDefinition as Record<string, unknown>,
522
522
  initialValue: value,
523
523
  }}
524
524
  >
525
525
  <EventListenerPlugin
526
- on={(event: any) => {
526
+ on={(event: Record<string, unknown>) => {
527
527
  if (event.type === "mutation" && event.value) {
528
- handleChange(event.value);
528
+ handleChange(event.value as Record<string, unknown>);
529
529
  }
530
530
  }}
531
531
  />
532
- <EditorInner onChange={handleChange} disabled={disabled} />
533
- </EditorProvider>
534
- );
532
+ <EditorInner onChange={handleChange} disabled={disabled} />
533
+ </EditorProvider>
534
+ );
535
535
  };
536
-
537
- export default EditorClient;