@jhits/plugin-blog 0.0.19 → 0.0.20

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 (291) hide show
  1. package/dist/api/categories.d.ts.map +1 -1
  2. package/dist/api/categories.js +42 -38
  3. package/dist/api/handler.d.ts +1 -26
  4. package/dist/api/handler.d.ts.map +1 -1
  5. package/dist/api/handler.js +81 -490
  6. package/dist/api/router.d.ts +0 -5
  7. package/dist/api/router.d.ts.map +1 -1
  8. package/dist/api/router.js +8 -35
  9. package/dist/api/service.d.ts +80 -0
  10. package/dist/api/service.d.ts.map +1 -0
  11. package/dist/api/service.js +219 -0
  12. package/dist/hooks/useAutoSave.d.ts +10 -0
  13. package/dist/hooks/useAutoSave.d.ts.map +1 -0
  14. package/dist/hooks/useAutoSave.js +57 -0
  15. package/dist/hooks/useCategories.d.ts +1 -1
  16. package/dist/hooks/useCategories.d.ts.map +1 -1
  17. package/dist/hooks/useCategories.js +15 -46
  18. package/dist/index.d.ts +24 -31
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +44 -201
  21. package/dist/init.d.ts +20 -7
  22. package/dist/init.d.ts.map +1 -1
  23. package/dist/init.js +8 -7
  24. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  25. package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
  26. package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
  27. package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
  28. package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
  29. package/dist/lib/layouts/index.d.ts +3 -3
  30. package/dist/lib/layouts/index.js +4 -4
  31. package/dist/lib/mappers/apiMapper.d.ts +10 -0
  32. package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
  33. package/dist/lib/mappers/apiMapper.js +47 -32
  34. package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
  35. package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
  36. package/dist/lib/rich-text/RichTextEditor.js +12 -9
  37. package/dist/lib/utils/config-resolver.d.ts +28 -0
  38. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  39. package/dist/lib/utils/config-resolver.js +46 -0
  40. package/dist/lib/utils/tree.d.ts +29 -0
  41. package/dist/lib/utils/tree.d.ts.map +1 -0
  42. package/dist/lib/utils/tree.js +129 -0
  43. package/dist/state/EditorContext.d.ts +3 -25
  44. package/dist/state/EditorContext.d.ts.map +1 -1
  45. package/dist/state/EditorContext.js +124 -174
  46. package/dist/state/reducer.d.ts +1 -5
  47. package/dist/state/reducer.d.ts.map +1 -1
  48. package/dist/state/reducer.js +128 -521
  49. package/dist/state/types.d.ts +12 -1
  50. package/dist/state/types.d.ts.map +1 -1
  51. package/dist/types/block.d.ts +9 -0
  52. package/dist/types/block.d.ts.map +1 -1
  53. package/dist/types/post.d.ts +17 -1
  54. package/dist/types/post.d.ts.map +1 -1
  55. package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
  56. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
  58. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
  59. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  60. package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
  61. package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
  62. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  63. package/dist/views/CanvasEditor/EditorBody.js +34 -482
  64. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  65. package/dist/views/CanvasEditor/EditorHeader.js +27 -63
  66. package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
  67. package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
  68. package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
  69. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
  70. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  71. package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
  72. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
  73. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
  74. package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
  75. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  76. package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
  77. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
  78. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
  79. package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
  80. package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
  81. package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
  82. package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
  83. package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
  84. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
  85. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
  86. package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
  87. package/dist/views/CanvasEditor/components/index.d.ts +2 -0
  88. package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
  89. package/dist/views/CanvasEditor/components/index.js +1 -0
  90. package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
  91. package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
  92. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
  93. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
  94. package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
  95. package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
  96. package/dist/views/PostManager/EmptyState.d.ts +1 -1
  97. package/dist/views/PostManager/EmptyState.js +4 -4
  98. package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
  99. package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
  100. package/dist/views/PostManager/FilterDropdown.js +28 -0
  101. package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
  102. package/dist/views/PostManager/LanguageFlags.js +4 -1
  103. package/dist/views/PostManager/PostCards.d.ts.map +1 -1
  104. package/dist/views/PostManager/PostCards.js +23 -40
  105. package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
  106. package/dist/views/PostManager/PostFilters.js +34 -3
  107. package/dist/views/PostManager/PostManagerView.d.ts +1 -2
  108. package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
  109. package/dist/views/PostManager/PostManagerView.js +30 -96
  110. package/dist/views/PostManager/PostStats.d.ts.map +1 -1
  111. package/dist/views/PostManager/PostStats.js +10 -10
  112. package/dist/views/PostManager/PostTable.d.ts.map +1 -1
  113. package/dist/views/PostManager/PostTable.js +23 -40
  114. package/dist/views/Settings/SettingsView.d.ts +1 -1
  115. package/dist/views/Settings/SettingsView.d.ts.map +1 -1
  116. package/dist/views/Settings/SettingsView.js +12 -39
  117. package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
  118. package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
  119. package/package.json +42 -6
  120. package/src/api/categories.ts +48 -52
  121. package/src/api/handler.ts +87 -594
  122. package/src/api/router.ts +15 -65
  123. package/src/api/service.ts +241 -0
  124. package/src/hooks/useAutoSave.ts +64 -0
  125. package/src/hooks/useCategories.ts +19 -47
  126. package/src/index.tsx +79 -293
  127. package/src/init.tsx +24 -11
  128. package/src/lib/blocks/BlockRenderer.tsx +1 -0
  129. package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
  130. package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
  131. package/src/lib/layouts/index.ts +4 -4
  132. package/src/lib/mappers/apiMapper.ts +63 -32
  133. package/src/lib/rich-text/RichTextEditor.tsx +16 -9
  134. package/src/lib/utils/config-resolver.ts +64 -0
  135. package/src/lib/utils/tree.ts +150 -0
  136. package/src/state/EditorContext.tsx +153 -232
  137. package/src/state/reducer.ts +141 -606
  138. package/src/state/types.ts +14 -1
  139. package/src/types/block.ts +10 -0
  140. package/src/types/post.ts +19 -1
  141. package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
  142. package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
  143. package/src/views/CanvasEditor/EditorBody.tsx +98 -610
  144. package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
  145. package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
  146. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
  147. package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
  148. package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
  149. package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
  150. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
  151. package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
  152. package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
  153. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
  154. package/src/views/CanvasEditor/components/index.ts +2 -1
  155. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
  156. package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
  157. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
  158. package/src/views/PostManager/EmptyState.tsx +9 -10
  159. package/src/views/PostManager/FilterDropdown.tsx +95 -0
  160. package/src/views/PostManager/LanguageFlags.tsx +6 -2
  161. package/src/views/PostManager/PostCards.tsx +127 -133
  162. package/src/views/PostManager/PostFilters.tsx +73 -68
  163. package/src/views/PostManager/PostManagerView.tsx +132 -179
  164. package/src/views/PostManager/PostStats.tsx +21 -20
  165. package/src/views/PostManager/PostTable.tsx +137 -165
  166. package/src/views/Settings/SettingsView.tsx +64 -180
  167. package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
  168. package/src/hooks/index.d.ts +0 -8
  169. package/src/hooks/index.d.ts.map +0 -1
  170. package/src/hooks/useBlog.d.ts +0 -31
  171. package/src/hooks/useBlog.d.ts.map +0 -1
  172. package/src/hooks/useBlogs.d.ts +0 -39
  173. package/src/hooks/useBlogs.d.ts.map +0 -1
  174. package/src/hooks/useCategories.d.ts +0 -9
  175. package/src/hooks/useCategories.d.ts.map +0 -1
  176. package/src/lib/blocks/BlockRenderer.d.ts +0 -54
  177. package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
  178. package/src/lib/config-storage.d.ts +0 -30
  179. package/src/lib/config-storage.d.ts.map +0 -1
  180. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
  181. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
  182. package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
  183. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
  184. package/src/lib/layouts/index.d.ts +0 -23
  185. package/src/lib/layouts/index.d.ts.map +0 -1
  186. package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
  187. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
  188. package/src/lib/mappers/apiMapper.d.ts +0 -66
  189. package/src/lib/mappers/apiMapper.d.ts.map +0 -1
  190. package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
  191. package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
  192. package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
  193. package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
  194. package/src/lib/rich-text/index.d.ts +0 -9
  195. package/src/lib/rich-text/index.d.ts.map +0 -1
  196. package/src/lib/utils/blockHelpers.d.ts +0 -23
  197. package/src/lib/utils/blockHelpers.d.ts.map +0 -1
  198. package/src/lib/utils/configValidation.d.ts +0 -23
  199. package/src/lib/utils/configValidation.d.ts.map +0 -1
  200. package/src/registry/BlockRegistry.d.ts +0 -62
  201. package/src/registry/BlockRegistry.d.ts.map +0 -1
  202. package/src/registry/index.d.ts +0 -6
  203. package/src/registry/index.d.ts.map +0 -1
  204. package/src/state/EditorContext.d.ts +0 -45
  205. package/src/state/EditorContext.d.ts.map +0 -1
  206. package/src/state/index.d.ts +0 -7
  207. package/src/state/index.d.ts.map +0 -1
  208. package/src/state/reducer.d.ts +0 -11
  209. package/src/state/reducer.d.ts.map +0 -1
  210. package/src/state/types.d.ts +0 -162
  211. package/src/state/types.d.ts.map +0 -1
  212. package/src/types/block.d.ts +0 -221
  213. package/src/types/block.d.ts.map +0 -1
  214. package/src/types/index.d.ts +0 -8
  215. package/src/types/index.d.ts.map +0 -1
  216. package/src/types/post.d.ts +0 -136
  217. package/src/types/post.d.ts.map +0 -1
  218. package/src/utils/client.d.ts +0 -48
  219. package/src/utils/client.d.ts.map +0 -1
  220. package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
  221. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
  222. package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
  223. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
  224. package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
  225. package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
  226. package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
  227. package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
  228. package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
  229. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
  230. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
  231. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
  232. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
  233. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
  234. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
  235. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
  236. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
  237. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
  238. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
  239. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
  240. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
  241. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
  242. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
  243. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
  244. package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
  245. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
  246. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
  247. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
  248. package/src/views/CanvasEditor/components/index.d.ts +0 -21
  249. package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
  250. package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
  251. package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
  252. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
  253. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
  254. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
  255. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
  256. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
  257. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
  258. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
  259. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
  260. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
  261. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
  262. package/src/views/CanvasEditor/index.d.ts +0 -16
  263. package/src/views/CanvasEditor/index.d.ts.map +0 -1
  264. package/src/views/PostManager/EmptyState.d.ts +0 -10
  265. package/src/views/PostManager/EmptyState.d.ts.map +0 -1
  266. package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
  267. package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
  268. package/src/views/PostManager/PostCards.d.ts +0 -15
  269. package/src/views/PostManager/PostCards.d.ts.map +0 -1
  270. package/src/views/PostManager/PostFilters.d.ts +0 -16
  271. package/src/views/PostManager/PostFilters.d.ts.map +0 -1
  272. package/src/views/PostManager/PostManagerView.d.ts +0 -11
  273. package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
  274. package/src/views/PostManager/PostStats.d.ts +0 -11
  275. package/src/views/PostManager/PostStats.d.ts.map +0 -1
  276. package/src/views/PostManager/PostTable.d.ts +0 -15
  277. package/src/views/PostManager/PostTable.d.ts.map +0 -1
  278. package/src/views/PostManager/index.d.ts +0 -12
  279. package/src/views/PostManager/index.d.ts.map +0 -1
  280. package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
  281. package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
  282. package/src/views/Preview/index.d.ts +0 -6
  283. package/src/views/Preview/index.d.ts.map +0 -1
  284. package/src/views/Settings/SettingsView.d.ts +0 -10
  285. package/src/views/Settings/SettingsView.d.ts.map +0 -1
  286. package/src/views/Settings/index.d.ts +0 -6
  287. package/src/views/Settings/index.d.ts.map +0 -1
  288. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
  289. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
  290. package/src/views/SlugSEO/index.d.ts +0 -6
  291. package/src/views/SlugSEO/index.d.ts.map +0 -1
@@ -2,38 +2,29 @@
2
2
 
3
3
  import React, { useState, useCallback, useEffect } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
- import { Image as ImageIcon, Plus, X } from 'lucide-react';
5
+ import { Image as ImageIcon, Plus, X, Edit2, Trash2 } from 'lucide-react';
6
6
  import { ImagePicker, Image } from '@jhits/plugin-images';
7
7
  import type { ImageMetadata } from '@jhits/plugin-images';
8
8
  import type { Block } from '../../../types/block';
9
9
 
10
10
  export interface FeaturedImage {
11
- // Store only the semantic ID - plugin-images handles everything else
12
11
  id?: string;
13
12
  alt?: string;
14
- // Transform values (stored locally for UI, but plugin-images API is source of truth)
15
13
  brightness?: number;
16
14
  blur?: number;
17
15
  scale?: number;
18
16
  positionX?: number;
19
17
  positionY?: number;
20
- // Indicates if this is a custom featured image (not synced from hero)
21
18
  isCustom?: boolean;
22
19
  }
23
20
 
24
21
  export interface FeaturedMediaSectionProps {
25
22
  featuredImage?: FeaturedImage;
26
- heroBlock?: Block | null; // Hero block to get default image from
27
- slug?: string; // Blog post slug for semantic ID
23
+ heroBlock?: Block | null;
24
+ slug?: string;
28
25
  onUpdate: (image: FeaturedImage | undefined) => void;
29
26
  }
30
27
 
31
- /**
32
- * Featured Media Section Component
33
- * Handles featured image selection - completely independent from hero image
34
- * Featured image is a thumbnail used for blog post cards
35
- * Hero image is separate and managed in the hero block
36
- */
37
28
  export function FeaturedMediaSection({
38
29
  featuredImage,
39
30
  heroBlock,
@@ -44,293 +35,171 @@ export function FeaturedMediaSection({
44
35
  const [openEditorDirectly, setOpenEditorDirectly] = useState(false);
45
36
  const [mounted, setMounted] = useState(false);
46
37
 
47
- // Handle SSR - ensure we only render portal on client
48
- useEffect(() => {
49
- setMounted(true);
50
- }, []);
38
+ useEffect(() => { setMounted(true); }, []);
51
39
 
52
- // Create semantic ID for this featured image - plugin-images will handle everything
53
40
  const semanticId = slug ? `blog-featured-${slug}` : `blog-featured-${Date.now()}`;
54
-
55
- // Use semantic ID from featuredImage if it exists, otherwise use generated one
56
- // IMPORTANT: Always use the actual id from featuredImage if available, otherwise the semanticId
57
- // This ensures the id is stable and doesn't change on re-renders
58
41
  const imageId = featuredImage?.id || semanticId;
59
42
 
60
- // Ensure featuredImage always has an id when it exists
61
- // This prevents the "missing featured image" issue on save
62
43
  useEffect(() => {
63
44
  if (featuredImage && !featuredImage.id) {
64
- // If featuredImage exists but has no id, set it to the semanticId
65
- onUpdate({
66
- ...featuredImage,
67
- id: semanticId,
68
- });
45
+ onUpdate({ ...featuredImage, id: semanticId });
69
46
  }
70
47
  }, [featuredImage, semanticId, onUpdate]);
71
48
 
72
- // Get transform values from featuredImage or use defaults
73
49
  const brightness = featuredImage?.brightness ?? 100;
74
50
  const blur = featuredImage?.blur ?? 0;
75
51
  const scale = featuredImage?.scale ?? 1.0;
76
52
  const positionX = featuredImage?.positionX ?? 0;
77
53
  const positionY = featuredImage?.positionY ?? 0;
78
54
 
79
- // Handle image selection - create initial mapping and update blog metadata with semantic ID
80
- // Plugin-images Image component will automatically resolve the semantic ID when it renders
81
55
  const handleImageChange = useCallback(async (image: ImageMetadata | null) => {
82
56
  if (image) {
83
- // Extract filename from image URL for reference
84
57
  const isUploadedImage = image.url.startsWith('/api/uploads/');
85
58
  let filename = image.filename;
59
+ if (!filename && isUploadedImage) filename = image.url.split('/api/uploads/')[1]?.split('?')[0] || image.id;
60
+ else if (!filename) filename = image.id || image.url.split('/').pop()?.split('?')[0] || `external-${Date.now()}`;
86
61
 
87
- if (!filename && isUploadedImage) {
88
- // Extract filename from URL if not provided
89
- filename = image.url.split('/api/uploads/')[1]?.split('?')[0] || image.id;
90
- } else if (!filename) {
91
- // For external URLs, use the image ID or extract from URL
92
- filename = image.id || image.url.split('/').pop()?.split('?')[0] || `external-${Date.now()}`;
93
- }
94
-
95
- // Create initial mapping in plugin-images API immediately
96
- // This ensures the semantic ID resolves correctly
97
62
  try {
98
- const saveData = {
99
- id: imageId,
100
- filename: filename,
101
- scale: 1.0,
102
- positionX: 0,
103
- positionY: 0,
104
- brightness: 100,
105
- blur: 0,
106
- };
107
-
63
+ const saveData = { id: imageId, filename, scale: 1.0, positionX: 0, positionY: 0, brightness: 100, blur: 0 };
108
64
  await fetch('/api/plugin-images/resolve', {
109
65
  method: 'POST',
110
66
  headers: { 'Content-Type': 'application/json' },
111
67
  body: JSON.stringify(saveData),
112
68
  });
113
- } catch (error) {
114
- console.error('[FeaturedMediaSection] Failed to create initial mapping:', error);
115
- // Continue anyway - the mapping might be created later
116
- }
69
+ } catch (error) { console.error(error); }
117
70
 
118
- // Update blog metadata with semantic ID
119
- onUpdate({
120
- id: imageId,
121
- alt: image.alt || image.filename,
122
- brightness: 100,
123
- blur: 0,
124
- scale: 1.0,
125
- positionX: 0,
126
- positionY: 0,
127
- isCustom: true,
128
- } as FeaturedImage);
71
+ onUpdate({ id: imageId, alt: image.alt || image.filename, brightness: 100, blur: 0, scale: 1.0, positionX: 0, positionY: 0, isCustom: true } as FeaturedImage);
129
72
  } else {
130
- // If removed, set to undefined
131
73
  onUpdate(undefined);
132
74
  }
133
75
  setShowImagePicker(false);
134
76
  }, [imageId, onUpdate]);
135
77
 
136
- // Handle editor save from ImagePicker - save to plugin-images API
137
- const handleEditorSave = useCallback(async (
138
- finalScale: number,
139
- finalPositionX: number,
140
- finalPositionY: number,
141
- finalBrightness?: number,
142
- finalBlur?: number
143
- ) => {
78
+ const handleEditorSave = useCallback(async (finalScale: number, finalPositionX: number, finalPositionY: number, finalBrightness?: number, finalBlur?: number) => {
144
79
  if (!featuredImage?.id) return;
145
-
146
- // Reset the auto-open flag immediately to prevent reopening
147
80
  setOpenEditorDirectly(false);
148
-
149
- // Get the actual filename from the API (resolve the semantic ID)
150
- let filename = imageId; // Fallback to semantic ID
81
+ let filename = imageId;
151
82
  try {
152
83
  const response = await fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(imageId)}`);
153
84
  if (response.ok) {
154
85
  const data = await response.json();
155
86
  filename = data.filename || imageId;
156
87
  }
157
- } catch (error) {
158
- console.error('Failed to resolve filename:', error);
159
- }
88
+ } catch (error) { console.error(error); }
160
89
 
161
- // Normalize position values
162
90
  const normalizedPositionX = finalPositionX === -50 ? 0 : finalPositionX;
163
91
  const normalizedPositionY = finalPositionY === -50 ? 0 : finalPositionY;
164
92
  const finalBrightnessValue = finalBrightness ?? brightness;
165
93
  const finalBlurValue = finalBlur ?? blur;
166
94
 
167
- // Save to plugin-images API
168
95
  try {
169
- const saveData = {
170
- id: imageId,
171
- filename: filename,
172
- scale: finalScale,
173
- positionX: normalizedPositionX,
174
- positionY: normalizedPositionY,
175
- brightness: finalBrightnessValue,
176
- blur: finalBlurValue,
177
- };
178
-
96
+ const saveData = { id: imageId, filename, scale: finalScale, positionX: normalizedPositionX, positionY: normalizedPositionY, brightness: finalBrightnessValue, blur: finalBlurValue };
179
97
  const response = await fetch('/api/plugin-images/resolve', {
180
98
  method: 'POST',
181
99
  headers: { 'Content-Type': 'application/json' },
182
100
  body: JSON.stringify(saveData),
183
101
  });
184
-
185
102
  if (response.ok) {
186
- // Update local featured image data - ensure id is preserved
187
- onUpdate({
188
- ...featuredImage,
189
- id: featuredImage.id || imageId, // Ensure id is always preserved
190
- scale: finalScale,
191
- positionX: normalizedPositionX,
192
- positionY: normalizedPositionY,
193
- brightness: finalBrightnessValue,
194
- blur: finalBlurValue,
195
- });
196
-
197
- // Dispatch event to notify Image components
198
- window.dispatchEvent(new CustomEvent('image-mapping-updated', {
199
- detail: saveData
200
- }));
103
+ onUpdate({ ...featuredImage, id: featuredImage.id || imageId, scale: finalScale, positionX: normalizedPositionX, positionY: normalizedPositionY, brightness: finalBrightnessValue, blur: finalBlurValue });
104
+ window.dispatchEvent(new CustomEvent('image-mapping-updated', { detail: saveData }));
201
105
  }
202
- } catch (error) {
203
- console.error('Failed to save image transform:', error);
204
- }
106
+ } catch (error) { console.error(error); }
205
107
  }, [imageId, featuredImage, brightness, blur, onUpdate]);
206
108
 
207
109
  return (
208
110
  <section>
209
111
  <div className="flex items-center gap-3 mb-6">
210
- <ImageIcon size={14} className="text-neutral-500 dark:text-neutral-400" />
211
- <label className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black">
212
- Featured Media
112
+ <div className="size-8 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
113
+ <ImageIcon size={14} />
114
+ </div>
115
+ <label className="text-[10px] font-black text-dashboard-text uppercase tracking-[0.3em]">
116
+ Visual Identity
213
117
  </label>
214
118
  </div>
119
+
215
120
  {featuredImage?.id ? (
216
121
  <div className="relative group">
217
- {/* Use Image component from plugin-images - it handles everything automatically */}
218
- {/* Blog component only handles the design/styling */}
219
- <div className="relative aspect-[16/10] bg-dashboard-bg rounded-3xl overflow-hidden border border-dashboard-border group/image">
122
+ <div className="relative aspect-[16/10] bg-dashboard-bg rounded-3xl overflow-hidden border border-dashboard-border group/image shadow-inner">
220
123
  <Image
221
124
  id={imageId}
222
125
  alt={featuredImage?.alt || 'Featured image'}
223
126
  fill
224
- className="object-cover w-full h-full"
225
- editable={false} // Disable Image component's overlay - we'll use our own
226
- {...({
227
- brightness,
228
- blur,
229
- scale,
230
- positionX,
231
- positionY,
232
- } as any)}
127
+ className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-700"
128
+ editable={false}
129
+ {...({ brightness, blur, scale, positionX, positionY } as any)}
233
130
  />
234
- {/* Custom edit overlay that opens ImagePicker editor */}
235
- <button
236
- onClick={() => {
237
- setOpenEditorDirectly(true);
238
- setShowImagePicker(true);
239
- }}
240
- className="absolute inset-0 z-30 flex items-center justify-center opacity-0 group-hover/image:opacity-100 transition-all duration-300 bg-neutral-900/40 dark:bg-neutral-900/60 backdrop-blur-[2px]"
241
- >
242
- <div className="flex items-center gap-2 px-4 py-2 bg-white dark:bg-neutral-800 rounded-full shadow-xl">
243
- <ImageIcon size={14} className="text-primary" />
244
- <span className="text-[10px] font-bold uppercase tracking-widest">Edit</span>
245
- </div>
246
- </button>
131
+ <div className="absolute inset-0 z-30 flex items-center justify-center opacity-0 group-hover/image:opacity-100 transition-all duration-500 bg-black/20 backdrop-blur-[2px]">
132
+ <button
133
+ onClick={() => { setOpenEditorDirectly(true); setShowImagePicker(true); }}
134
+ className="flex items-center gap-2 px-5 py-2.5 bg-white text-black rounded-full shadow-2xl hover:scale-105 active:scale-95 transition-all"
135
+ >
136
+ <Edit2 size={12} className="fill-current" />
137
+ <span className="text-[10px] font-black uppercase tracking-widest">Adjust View</span>
138
+ </button>
139
+ </div>
247
140
  </div>
248
- <div className="mt-2 flex items-center gap-3">
141
+ <div className="mt-4 flex items-center justify-between px-1">
249
142
  <button
250
143
  onClick={() => setShowImagePicker(true)}
251
- className="text-[10px] text-neutral-600 dark:text-neutral-400 hover:text-primary font-bold uppercase tracking-wider"
144
+ className="text-[9px] font-black uppercase tracking-[0.2em] text-primary hover:text-primary/80 transition-colors flex items-center gap-1.5"
252
145
  >
253
- Change Image
146
+ <Plus size={10} strokeWidth={3} />
147
+ Replace
254
148
  </button>
255
- <span className="text-[10px] text-neutral-400">•</span>
256
149
  <button
257
150
  onClick={() => onUpdate(undefined)}
258
- className="text-[10px] text-red-500 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 font-bold uppercase tracking-wider"
151
+ className="text-[9px] font-black uppercase tracking-[0.2em] text-red-500/60 hover:text-red-500 transition-colors flex items-center gap-1.5"
259
152
  >
260
- Remove Image
153
+ <Trash2 size={10} />
154
+ Detach
261
155
  </button>
262
156
  </div>
263
157
  </div>
264
158
  ) : (
265
159
  <div
266
- className="group relative aspect-[16/10] bg-dashboard-bg rounded-3xl border-2 border-dashed border-dashboard-border flex flex-col items-center justify-center text-neutral-400 dark:text-neutral-500 hover:bg-dashboard-card hover:border-primary cursor-pointer transition-all duration-300"
160
+ className="group relative aspect-[16/10] bg-dashboard-bg/50 rounded-[2.5rem] border-2 border-dashed border-dashboard-border/50 flex flex-col items-center justify-center text-dashboard-text-secondary/40 hover:bg-primary/[0.02] hover:border-primary/40 cursor-pointer transition-all duration-500 shadow-inner"
267
161
  onClick={() => setShowImagePicker(true)}
268
162
  >
269
- <Plus size={24} strokeWidth={1} className="mb-3 group-hover:scale-110 transition-transform" />
270
- <span className="text-[9px] font-black uppercase tracking-widest">Assign Image</span>
163
+ <div className="size-14 rounded-full bg-dashboard-card flex items-center justify-center border border-dashboard-border group-hover:scale-110 group-hover:border-primary/20 transition-all duration-500 mb-4 shadow-sm">
164
+ <ImageIcon size={24} strokeWidth={1.5} className="group-hover:text-primary transition-colors" />
165
+ </div>
166
+ <span className="text-[10px] font-black uppercase tracking-[0.3em] group-hover:text-primary transition-colors">Assign Visual</span>
271
167
  </div>
272
168
  )}
273
169
 
274
- {/* Image Picker Modal */}
275
170
  {showImagePicker && mounted && createPortal(
276
- <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 backdrop-blur-sm" onClick={() => {
277
- setShowImagePicker(false);
278
- setOpenEditorDirectly(false); // Reset flag when closing
279
- }}>
280
- <div className="bg-white dark:bg-neutral-900 rounded-2xl w-full max-w-2xl mx-4 p-6 shadow-2xl max-h-[90vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
281
- <div className="flex items-center justify-between mb-6">
282
- <h3 className="text-lg font-bold text-neutral-900 dark:text-neutral-100">
283
- {openEditorDirectly ? 'Edit Featured Image' : 'Select Featured Image'}
284
- </h3>
285
- <button
286
- onClick={() => {
287
- setShowImagePicker(false);
288
- setOpenEditorDirectly(false); // Reset flag when closing
289
- }}
290
- className="p-2 hover:bg-dashboard-bg dark:hover:bg-neutral-800 rounded-lg transition-colors text-neutral-700 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-white"
291
- aria-label="Close"
292
- >
293
- <X size={20} className="transition-colors" />
171
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-6" onClick={() => { setShowImagePicker(false); setOpenEditorDirectly(false); }}>
172
+ <div className="absolute inset-0 bg-black/60 backdrop-blur-md" />
173
+ <div className="relative bg-dashboard-bg border border-dashboard-border rounded-[3rem] w-full max-w-3xl overflow-hidden shadow-2xl animate-in zoom-in-95 duration-300 flex flex-col max-h-full" onClick={(e) => e.stopPropagation()}>
174
+ <div className="p-8 border-b border-dashboard-border flex items-center justify-between">
175
+ <div>
176
+ <h3 className="text-2xl font-black text-dashboard-text uppercase tracking-tighter">
177
+ {openEditorDirectly ? 'Perfecting' : 'Curating'} Image
178
+ </h3>
179
+ <p className="text-xs text-dashboard-text-secondary italic mt-1">Select or adjust the featured visual for your article.</p>
180
+ </div>
181
+ <button onClick={() => { setShowImagePicker(false); setOpenEditorDirectly(false); }} className="size-12 flex items-center justify-center bg-dashboard-card border border-dashboard-border rounded-2xl hover:text-red-500 hover:border-red-500/20 transition-all">
182
+ <X size={20} />
294
183
  </button>
295
184
  </div>
296
- <ImagePicker
297
- value={featuredImage?.id ? imageId : undefined}
298
- onChange={handleImageChange}
299
- brightness={brightness}
300
- blur={blur}
301
- {...({
302
- scale,
303
- positionX,
304
- positionY,
305
- } as any)}
306
- onBrightnessChange={(val) => {
307
- // Update local state only - don't trigger save
308
- if (featuredImage) {
309
- onUpdate({
310
- ...featuredImage,
311
- id: featuredImage.id || imageId, // Ensure id is preserved
312
- brightness: val,
313
- });
314
- }
315
- }}
316
- onBlurChange={(val) => {
317
- // Update local state only - don't trigger save
318
- if (featuredImage) {
319
- onUpdate({
320
- ...featuredImage,
321
- id: featuredImage.id || imageId, // Ensure id is preserved
322
- blur: val,
323
- });
324
- }
325
- }}
326
- onEditorSave={handleEditorSave}
327
- darkMode={false}
328
- showEffects={true} // Enable effects so editor can be used
329
- aspectRatio="16/10" // Thumbnail aspect ratio for blog cards
330
- borderRadius="rounded-3xl"
331
- objectFit="cover" // Cover for thumbnails
332
- objectPosition="center"
333
- />
185
+ <div className="flex-1 overflow-y-auto p-8 custom-scrollbar">
186
+ <ImagePicker
187
+ value={featuredImage?.id ? imageId : undefined}
188
+ onChange={handleImageChange}
189
+ brightness={brightness}
190
+ blur={blur}
191
+ {...({ scale, positionX, positionY } as any)}
192
+ onBrightnessChange={(val) => { if (featuredImage) onUpdate({ ...featuredImage, id: featuredImage.id || imageId, brightness: val }); }}
193
+ onBlurChange={(val) => { if (featuredImage) onUpdate({ ...featuredImage, id: featuredImage.id || imageId, blur: val }); }}
194
+ onEditorSave={handleEditorSave}
195
+ darkMode={true}
196
+ showEffects={true}
197
+ aspectRatio="16/10"
198
+ borderRadius="rounded-[2rem]"
199
+ objectFit="cover"
200
+ objectPosition="center"
201
+ />
202
+ </div>
334
203
  </div>
335
204
  </div>,
336
205
  document.body
@@ -338,4 +207,3 @@ export function FeaturedMediaSection({
338
207
  </section>
339
208
  );
340
209
  }
341
-
@@ -0,0 +1,125 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useMemo } from 'react';
4
+ import { Code, X, Copy, Check } from 'lucide-react';
5
+
6
+ export interface JSONInspectorProps {
7
+ data: any;
8
+ }
9
+
10
+ /**
11
+ * Simple JSON Syntax Highlighter
12
+ */
13
+ function highlightJSON(json: string) {
14
+ if (!json) return null;
15
+
16
+ // Convert to string and escape HTML
17
+ const escaped = json
18
+ .replace(/&/g, '&amp;')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;');
21
+
22
+ return escaped.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
23
+ let cls = 'text-orange-400'; // number
24
+ if (/^"/.test(match)) {
25
+ if (/:$/.test(match)) {
26
+ cls = 'text-blue-400'; // key
27
+ } else {
28
+ cls = 'text-green-400'; // string
29
+ }
30
+ } else if (/true|false/.test(match)) {
31
+ cls = 'text-purple-400'; // boolean
32
+ } else if (/null/.test(match)) {
33
+ cls = 'text-neutral-500'; // null
34
+ }
35
+ return `<span class="${cls}">${match}</span>`;
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Dev-only JSON Inspector
41
+ * Displays the current state of the blog post in a formatted JSON view
42
+ */
43
+ export function JSONInspector({ data }: JSONInspectorProps) {
44
+ const [isOpen, setIsOpen] = useState(false);
45
+ const [copied, setCopied] = useState(false);
46
+
47
+ // Memoize the highlighted HTML to prevent re-parsing on every render
48
+ const highlightedHTML = useMemo(() => {
49
+ const jsonString = JSON.stringify(data, null, 2);
50
+ return highlightJSON(jsonString);
51
+ }, [data]);
52
+
53
+ // Only visible in development mode
54
+ if (process.env.NODE_ENV !== 'development') return null;
55
+
56
+ const handleCopy = () => {
57
+ navigator.clipboard.writeText(JSON.stringify(data, null, 2));
58
+ setCopied(true);
59
+ setTimeout(() => setCopied(false), 2000);
60
+ };
61
+
62
+ return (
63
+ <>
64
+ {/* Floating Toggle Button */}
65
+ <button
66
+ onClick={() => setIsOpen(true)}
67
+ className="fixed bottom-8 right-8 z-[100] bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 px-5 py-3 rounded-2xl shadow-2xl hover:scale-105 active:scale-95 transition-all flex items-center gap-3 border border-white/10 dark:border-neutral-200"
68
+ title="Inspect JSON (Dev Only)"
69
+ >
70
+ <div className="size-2 rounded-full bg-green-500 animate-pulse" />
71
+ <Code size={18} strokeWidth={2.5} />
72
+ <span className="text-[10px] font-black uppercase tracking-[0.2em]">Inspect JSON</span>
73
+ </button>
74
+
75
+ {/* Modal Overlay */}
76
+ {isOpen && (
77
+ <div className="fixed inset-0 z-[110] bg-black/60 backdrop-blur-md flex items-center justify-center p-6 md:p-12 animate-in fade-in duration-300">
78
+ <div className="bg-white dark:bg-neutral-900 rounded-[2.5rem] w-full max-w-5xl h-[85vh] flex flex-col overflow-hidden shadow-[0_32px_64px_-12px_rgba(0,0,0,0.5)] border border-neutral-200 dark:border-neutral-800 animate-in zoom-in-95 duration-300">
79
+
80
+ {/* Header */}
81
+ <div className="px-8 py-6 border-b border-neutral-100 dark:border-neutral-800 flex justify-between items-center bg-neutral-50/50 dark:bg-neutral-800/50 shrink-0">
82
+ <div>
83
+ <div className="flex items-center gap-3 mb-1">
84
+ <h3 className="font-black uppercase tracking-tighter text-2xl text-neutral-900 dark:text-white">Blog Data</h3>
85
+ <span className="px-2 py-0.5 rounded-md bg-primary/10 text-primary text-[9px] font-black uppercase tracking-widest">Dev Tool</span>
86
+ </div>
87
+ <p className="text-[10px] text-neutral-500 font-bold uppercase tracking-widest">Raw state inspector for debugging purposes</p>
88
+ </div>
89
+
90
+ <div className="flex items-center gap-3">
91
+ <button
92
+ onClick={handleCopy}
93
+ className="flex items-center gap-2 px-5 py-2.5 bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all"
94
+ >
95
+ {copied ? <Check size={14} className="text-green-500" /> : <Copy size={14} />}
96
+ {copied ? 'Copied' : 'Copy JSON'}
97
+ </button>
98
+ <button
99
+ onClick={() => setIsOpen(false)}
100
+ className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-colors text-neutral-400 hover:text-neutral-900 dark:hover:text-white"
101
+ >
102
+ <X size={28} strokeWidth={1.5} />
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ {/* Content Area - Fixed scrolling and added syntax highlighting */}
108
+ <div className="flex-1 min-h-0 w-full bg-[#0d0d0d] overflow-y-auto overflow-x-hidden custom-scrollbar">
109
+ <pre
110
+ className="p-8 font-mono text-[13px] leading-relaxed text-neutral-300 whitespace-pre-wrap break-all"
111
+ dangerouslySetInnerHTML={{ __html: highlightedHTML || '' }}
112
+ />
113
+ </div>
114
+
115
+ {/* Footer */}
116
+ <div className="px-8 py-4 bg-neutral-50 dark:bg-neutral-800/30 border-t border-neutral-100 dark:border-neutral-800 flex justify-between items-center text-[9px] font-bold text-neutral-400 uppercase tracking-widest shrink-0">
117
+ <span>Blocks Count: {data.blocks?.length || 0}</span>
118
+ <span>Status: {data.status || 'Unknown'}</span>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ )}
123
+ </>
124
+ );
125
+ }
@@ -25,7 +25,7 @@ export function LibraryItem({
25
25
  const mouseDownRef = useRef<{ x: number; y: number } | null>(null);
26
26
 
27
27
  const handleDragStart = (e: React.DragEvent) => {
28
- e.dataTransfer.setData('block-type', blockType);
28
+ e.dataTransfer.setData('blockType', blockType);
29
29
  e.dataTransfer.effectAllowed = 'move';
30
30
  setHasDragged(true); // Mark as dragged when drag starts
31
31
  };
@@ -66,15 +66,14 @@ export function LibraryItem({
66
66
  onMouseDown={handleMouseDown}
67
67
  onMouseMove={handleMouseMove}
68
68
  onClick={handleClick}
69
- className="flex flex-col items-center justify-center p-5 rounded-2xl border border-dashboard-border bg-dashboard-card hover:border-primary hover:shadow-xl hover:shadow-primary/5 transition-all cursor-pointer group"
69
+ className="flex flex-col items-center justify-center p-6 rounded-[2rem] border border-dashboard-border/50 bg-dashboard-card/30 backdrop-blur-sm hover:border-primary/40 hover:bg-primary/[0.02] hover:shadow-2xl hover:shadow-primary/10 hover:scale-[1.05] transition-all duration-500 cursor-grab active:cursor-grabbing group"
70
70
  >
71
- <div className="text-neutral-400 dark:text-neutral-500 group-hover:text-primary dark:group-hover:text-primary mb-3 transition-colors duration-300">
72
- {React.cloneElement(icon as React.ReactElement, { strokeWidth: 1.5 } as any)}
71
+ <div className="text-dashboard-text-secondary group-hover:text-primary transition-all duration-500 group-hover:scale-110 mb-4">
72
+ {React.cloneElement(icon as React.ReactElement, { strokeWidth: 1.5, size: 24 } as any)}
73
73
  </div>
74
- <span className="text-[9px] font-black uppercase tracking-[0.15em] text-neutral-600 dark:text-neutral-400 group-hover:text-neutral-950 dark:group-hover:text-white transition-colors">
74
+ <span className="text-[9px] font-black uppercase tracking-[0.2em] text-dashboard-text-secondary group-hover:text-dashboard-text transition-colors text-center leading-tight">
75
75
  {label}
76
76
  </span>
77
77
  </div>
78
78
  );
79
79
  }
80
-