@prenta/admin 0.98.0 → 1.0.0

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 (270) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +17 -28
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/components/seo-score-bands.test.js +0 -10
  6. package/dist/__tests__/components/seo-score-bands.test.js.map +1 -1
  7. package/dist/__tests__/layout/sidebar-posts.test.js +6 -11
  8. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
  9. package/dist/__tests__/layout/sidebar-submenu.render.test.js +3 -2
  10. package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
  11. package/dist/__tests__/lib/page-audit-input.test.js +77 -1
  12. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -1
  13. package/dist/__tests__/lib/page-editor-service-api.test.js +26 -23
  14. package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
  15. package/dist/__tests__/views/page-section-editor.test.js +0 -1
  16. package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
  17. package/dist/__tests__/views/page-tab.render.test.js +0 -1
  18. package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
  19. package/dist/index.d.ts +1 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/layout/Sidebar.d.ts +3 -4
  24. package/dist/layout/Sidebar.d.ts.map +1 -1
  25. package/dist/layout/Sidebar.js +7 -38
  26. package/dist/layout/Sidebar.js.map +1 -1
  27. package/dist/lib/page-audit-input.d.ts +14 -1
  28. package/dist/lib/page-audit-input.d.ts.map +1 -1
  29. package/dist/lib/page-audit-input.js +19 -0
  30. package/dist/lib/page-audit-input.js.map +1 -1
  31. package/dist/lib/page-editor-service.d.ts +0 -7
  32. package/dist/lib/page-editor-service.d.ts.map +1 -1
  33. package/dist/lib/page-editor-service.js +9 -9
  34. package/dist/lib/page-editor-service.js.map +1 -1
  35. package/dist/prenta-admin.css +1 -1
  36. package/dist/views/PageEditor.d.ts +2 -2
  37. package/dist/views/PageEditor.js +2 -2
  38. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  39. package/dist/views/page-editor/PageSectionEditor.js +9 -15
  40. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  41. package/package.json +4 -4
  42. package/src/AdminRoot.tsx +18 -68
  43. package/src/__tests__/components/seo-score-bands.test.ts +0 -10
  44. package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
  45. package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
  46. package/src/__tests__/lib/page-audit-input.test.ts +91 -1
  47. package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
  48. package/src/__tests__/views/page-section-editor.test.tsx +0 -1
  49. package/src/__tests__/views/page-tab.render.test.tsx +0 -1
  50. package/src/index.ts +1 -11
  51. package/src/layout/Sidebar.tsx +6 -41
  52. package/src/lib/page-audit-input.ts +36 -1
  53. package/src/lib/page-editor-service.ts +9 -16
  54. package/src/views/PageEditor.tsx +2 -2
  55. package/src/views/page-editor/PageSectionEditor.tsx +8 -37
  56. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
  57. package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
  58. package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
  59. package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
  60. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
  61. package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
  62. package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
  63. package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
  64. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
  65. package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
  66. package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
  67. package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
  68. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
  69. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
  70. package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
  71. package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
  72. package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
  73. package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
  74. package/dist/__tests__/views/page-templates.render.test.js +0 -126
  75. package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
  76. package/dist/hooks/useBuilderState.d.ts +0 -49
  77. package/dist/hooks/useBuilderState.d.ts.map +0 -1
  78. package/dist/hooks/useBuilderState.js +0 -238
  79. package/dist/hooks/useBuilderState.js.map +0 -1
  80. package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
  81. package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
  82. package/dist/views/page-builder/AIBlockAssist.js +0 -40
  83. package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
  84. package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
  85. package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
  86. package/dist/views/page-builder/AIGenerateDialog.js +0 -173
  87. package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
  88. package/dist/views/page-builder/BlockEditor.d.ts +0 -11
  89. package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
  90. package/dist/views/page-builder/BlockEditor.js +0 -159
  91. package/dist/views/page-builder/BlockEditor.js.map +0 -1
  92. package/dist/views/page-builder/BlockPicker.d.ts +0 -7
  93. package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
  94. package/dist/views/page-builder/BlockPicker.js +0 -103
  95. package/dist/views/page-builder/BlockPicker.js.map +0 -1
  96. package/dist/views/page-builder/BottomBar.d.ts +0 -9
  97. package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
  98. package/dist/views/page-builder/BottomBar.js +0 -13
  99. package/dist/views/page-builder/BottomBar.js.map +0 -1
  100. package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
  101. package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
  102. package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
  103. package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
  104. package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
  105. package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
  106. package/dist/views/page-builder/BuilderToolbar.js +0 -18
  107. package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
  108. package/dist/views/page-builder/ContextPanel.d.ts +0 -20
  109. package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
  110. package/dist/views/page-builder/ContextPanel.js +0 -43
  111. package/dist/views/page-builder/ContextPanel.js.map +0 -1
  112. package/dist/views/page-builder/DesignScore.d.ts +0 -6
  113. package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
  114. package/dist/views/page-builder/DesignScore.js +0 -93
  115. package/dist/views/page-builder/DesignScore.js.map +0 -1
  116. package/dist/views/page-builder/NodeSettings.d.ts +0 -12
  117. package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
  118. package/dist/views/page-builder/NodeSettings.js +0 -80
  119. package/dist/views/page-builder/NodeSettings.js.map +0 -1
  120. package/dist/views/page-builder/PageBuilder.d.ts +0 -10
  121. package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
  122. package/dist/views/page-builder/PageBuilder.js +0 -222
  123. package/dist/views/page-builder/PageBuilder.js.map +0 -1
  124. package/dist/views/page-builder/PageSettings.d.ts +0 -7
  125. package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
  126. package/dist/views/page-builder/PageSettings.js +0 -27
  127. package/dist/views/page-builder/PageSettings.js.map +0 -1
  128. package/dist/views/page-builder/PageTemplates.d.ts +0 -5
  129. package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
  130. package/dist/views/page-builder/PageTemplates.js +0 -146
  131. package/dist/views/page-builder/PageTemplates.js.map +0 -1
  132. package/dist/views/page-builder/RelationshipField.d.ts +0 -15
  133. package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
  134. package/dist/views/page-builder/RelationshipField.js +0 -65
  135. package/dist/views/page-builder/RelationshipField.js.map +0 -1
  136. package/dist/views/page-builder/SEOPanel.d.ts +0 -10
  137. package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
  138. package/dist/views/page-builder/SEOPanel.js +0 -103
  139. package/dist/views/page-builder/SEOPanel.js.map +0 -1
  140. package/dist/views/page-builder/SavedSections.d.ts +0 -6
  141. package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
  142. package/dist/views/page-builder/SavedSections.js +0 -141
  143. package/dist/views/page-builder/SavedSections.js.map +0 -1
  144. package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
  145. package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
  146. package/dist/views/page-builder/TemplatePicker.js +0 -68
  147. package/dist/views/page-builder/TemplatePicker.js.map +0 -1
  148. package/dist/views/page-builder/Widgets.d.ts +0 -13
  149. package/dist/views/page-builder/Widgets.d.ts.map +0 -1
  150. package/dist/views/page-builder/Widgets.js +0 -15
  151. package/dist/views/page-builder/Widgets.js.map +0 -1
  152. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
  153. package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
  154. package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
  155. package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
  156. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
  157. package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
  158. package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
  159. package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
  160. package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
  161. package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
  162. package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
  163. package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
  164. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
  165. package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
  166. package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
  167. package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
  168. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
  169. package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
  170. package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
  171. package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
  172. package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
  173. package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
  174. package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
  175. package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
  176. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
  177. package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
  178. package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
  179. package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
  180. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
  181. package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
  182. package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
  183. package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
  184. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
  185. package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
  186. package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
  187. package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
  188. package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
  189. package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
  190. package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
  191. package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
  192. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
  193. package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
  194. package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
  195. package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
  196. package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
  197. package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
  198. package/dist/views/page-builder/block-renderers/index.js +0 -25
  199. package/dist/views/page-builder/block-renderers/index.js.map +0 -1
  200. package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
  201. package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
  202. package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
  203. package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
  204. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
  205. package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
  206. package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
  207. package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
  208. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
  209. package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
  210. package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
  211. package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
  212. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
  213. package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
  214. package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
  215. package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
  216. package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
  217. package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
  218. package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
  219. package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
  220. package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
  221. package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
  222. package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
  223. package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
  224. package/dist/views/page-builder/canvas/index.d.ts +0 -3
  225. package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
  226. package/dist/views/page-builder/canvas/index.js +0 -2
  227. package/dist/views/page-builder/canvas/index.js.map +0 -1
  228. package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
  229. package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
  230. package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
  231. package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
  232. package/src/__tests__/views/page-templates.render.test.tsx +0 -182
  233. package/src/hooks/useBuilderState.ts +0 -361
  234. package/src/views/page-builder/AIBlockAssist.tsx +0 -64
  235. package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
  236. package/src/views/page-builder/BlockEditor.tsx +0 -523
  237. package/src/views/page-builder/BlockPicker.tsx +0 -324
  238. package/src/views/page-builder/BottomBar.tsx +0 -65
  239. package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
  240. package/src/views/page-builder/BuilderToolbar.tsx +0 -221
  241. package/src/views/page-builder/ContextPanel.tsx +0 -145
  242. package/src/views/page-builder/DesignScore.tsx +0 -251
  243. package/src/views/page-builder/NodeSettings.tsx +0 -500
  244. package/src/views/page-builder/PageBuilder.tsx +0 -405
  245. package/src/views/page-builder/PageSettings.tsx +0 -162
  246. package/src/views/page-builder/PageTemplates.tsx +0 -435
  247. package/src/views/page-builder/RelationshipField.tsx +0 -113
  248. package/src/views/page-builder/SEOPanel.tsx +0 -464
  249. package/src/views/page-builder/SavedSections.tsx +0 -480
  250. package/src/views/page-builder/TemplatePicker.tsx +0 -197
  251. package/src/views/page-builder/Widgets.tsx +0 -44
  252. package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
  253. package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
  254. package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
  255. package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
  256. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
  257. package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
  258. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
  259. package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
  260. package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
  261. package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
  262. package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
  263. package/src/views/page-builder/block-renderers/index.ts +0 -34
  264. package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
  265. package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
  266. package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
  267. package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
  268. package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
  269. package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
  270. package/src/views/page-builder/canvas/index.ts +0 -2
@@ -1,324 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo, useRef, useEffect, useCallback } from 'react'
4
- import * as Dialog from '@radix-ui/react-dialog'
5
- import { adminPortalContainer } from '../../lib/portal-container.js'
6
- import {
7
- Star,
8
- Type,
9
- Image,
10
- LayoutGrid,
11
- MousePointer,
12
- Play,
13
- Grid,
14
- HelpCircle,
15
- FileText,
16
- Code,
17
- Box,
18
- X,
19
- ArrowLeft,
20
- Search,
21
- Check,
22
- } from 'lucide-react'
23
- import type { LucideIcon } from 'lucide-react'
24
- import { BlockCatalog } from '@prenta/core/page-builder'
25
- import type { BlockTypeDefinition, VariantDefinition } from '@prenta/core'
26
-
27
- export interface BlockPickerProps {
28
- open: boolean
29
- onClose: () => void
30
- onSelect: (blockType: string, variant?: string) => void
31
- }
32
-
33
- const iconMap: Record<string, LucideIcon> = {
34
- Star,
35
- Type,
36
- Image,
37
- LayoutGrid,
38
- MousePointer,
39
- Play,
40
- Grid,
41
- HelpCircle,
42
- FileText,
43
- Code,
44
- }
45
-
46
- function resolveIcon(name: string): LucideIcon {
47
- return iconMap[name] ?? Box
48
- }
49
-
50
- export function BlockPicker({ open, onClose, onSelect }: BlockPickerProps) {
51
- const [search, setSearch] = useState('')
52
- const [selectedBlock, setSelectedBlock] = useState<BlockTypeDefinition | null>(null)
53
- const searchInputRef = useRef<HTMLInputElement>(null)
54
- const variantListRef = useRef<HTMLDivElement>(null)
55
-
56
- const catalog = useMemo(() => new BlockCatalog(), [])
57
- const allBlocks = useMemo(() => catalog.getAll(), [catalog])
58
-
59
- const filteredBlocks = useMemo(() => {
60
- if (!search.trim()) return allBlocks
61
- const q = search.toLowerCase()
62
- return allBlocks.filter(
63
- (block) =>
64
- block.label.toLowerCase().includes(q) ||
65
- (block.description?.toLowerCase().includes(q) ?? false),
66
- )
67
- }, [allBlocks, search])
68
-
69
- const resetState = useCallback(() => {
70
- setSearch('')
71
- setSelectedBlock(null)
72
- }, [])
73
-
74
- useEffect(() => {
75
- if (open) {
76
- resetState()
77
- }
78
- }, [open, resetState])
79
-
80
- useEffect(() => {
81
- if (open && !selectedBlock) {
82
- const timer = setTimeout(() => searchInputRef.current?.focus(), 50)
83
- return () => clearTimeout(timer)
84
- }
85
- }, [open, selectedBlock])
86
-
87
- useEffect(() => {
88
- if (selectedBlock && variantListRef.current) {
89
- const firstButton =
90
- variantListRef.current.querySelector<HTMLButtonElement>('button[data-variant]')
91
- firstButton?.focus()
92
- }
93
- }, [selectedBlock])
94
-
95
- function handleBlockClick(block: BlockTypeDefinition) {
96
- if (block.variants.length === 1) {
97
- onSelect(block.type, block.variants[0]!.name)
98
- onClose()
99
- return
100
- }
101
- setSelectedBlock(block)
102
- }
103
-
104
- function handleVariantClick(variant: VariantDefinition) {
105
- if (!selectedBlock) return
106
- onSelect(selectedBlock.type, variant.name)
107
- onClose()
108
- }
109
-
110
- function handleBack() {
111
- setSelectedBlock(null)
112
- }
113
-
114
- function handleKeyDownOnGrid(e: React.KeyboardEvent) {
115
- if (e.key === 'Escape') {
116
- e.stopPropagation()
117
- onClose()
118
- }
119
- }
120
-
121
- function handleKeyDownOnVariants(e: React.KeyboardEvent) {
122
- if (e.key === 'Escape') {
123
- e.stopPropagation()
124
- handleBack()
125
- }
126
- }
127
-
128
- return (
129
- <Dialog.Root open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
130
- <Dialog.Portal container={adminPortalContainer()}>
131
- <Dialog.Overlay className="motion-safe:animate-in motion-safe:fade-in-0 fixed inset-0 z-50 bg-black/50" />
132
- <Dialog.Content
133
- className="bg-card border-border fixed top-1/2 left-1/2 z-50 flex max-h-[80vh] w-full max-w-2xl -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border shadow-2xl"
134
- aria-describedby={undefined}
135
- onEscapeKeyDown={() => {
136
- if (selectedBlock) {
137
- handleBack()
138
- }
139
- }}
140
- >
141
- {selectedBlock ? (
142
- <VariantView
143
- block={selectedBlock}
144
- variantListRef={variantListRef}
145
- onBack={handleBack}
146
- onVariantClick={handleVariantClick}
147
- onKeyDown={handleKeyDownOnVariants}
148
- />
149
- ) : (
150
- <BlockTypeGrid
151
- search={search}
152
- filteredBlocks={filteredBlocks}
153
- searchInputRef={searchInputRef}
154
- onSearchChange={setSearch}
155
- onBlockClick={handleBlockClick}
156
- onClose={onClose}
157
- onKeyDown={handleKeyDownOnGrid}
158
- />
159
- )}
160
- </Dialog.Content>
161
- </Dialog.Portal>
162
- </Dialog.Root>
163
- )
164
- }
165
-
166
- interface BlockTypeGridProps {
167
- search: string
168
- filteredBlocks: BlockTypeDefinition[]
169
- searchInputRef: React.RefObject<HTMLInputElement | null>
170
- onSearchChange: (value: string) => void
171
- onBlockClick: (block: BlockTypeDefinition) => void
172
- onClose: () => void
173
- onKeyDown: (e: React.KeyboardEvent) => void
174
- }
175
-
176
- function BlockTypeGrid({
177
- search,
178
- filteredBlocks,
179
- searchInputRef,
180
- onSearchChange,
181
- onBlockClick,
182
- onClose,
183
- onKeyDown,
184
- }: BlockTypeGridProps) {
185
- return (
186
- <div className="flex h-full flex-col" onKeyDown={onKeyDown}>
187
- <div className="flex items-center justify-between gap-3 px-5 pt-5 pb-3">
188
- <Dialog.Title className="text-foreground text-lg font-medium">Add Block</Dialog.Title>
189
- <Dialog.Close asChild>
190
- <button
191
- type="button"
192
- className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-md p-1.5 transition-colors"
193
- aria-label="Close"
194
- >
195
- <X size={18} />
196
- </button>
197
- </Dialog.Close>
198
- </div>
199
-
200
- <div className="px-5 pb-3">
201
- <div className="relative">
202
- <Search
203
- className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 -translate-y-1/2"
204
- size={16}
205
- />
206
- <input
207
- ref={searchInputRef}
208
- type="text"
209
- placeholder="Search blocks..."
210
- value={search}
211
- onChange={(e) => onSearchChange(e.target.value)}
212
- className="bg-background border-input focus:ring-ring w-full rounded-md border py-2 pr-3 pl-9 text-sm focus:ring-2 focus:outline-none"
213
- aria-label="Search block types"
214
- />
215
- </div>
216
- </div>
217
-
218
- <div className="flex-1 overflow-y-auto px-5 pb-5">
219
- {filteredBlocks.length === 0 ? (
220
- <div className="flex flex-col items-center justify-center py-12 text-center">
221
- <Search size={24} className="text-muted-foreground mb-2" />
222
- <p className="text-muted-foreground text-sm">No blocks match &ldquo;{search}&rdquo;</p>
223
- </div>
224
- ) : (
225
- <div className="grid grid-cols-2 gap-3" role="list" aria-label="Available block types">
226
- {filteredBlocks.map((block) => {
227
- const Icon = resolveIcon(block.icon)
228
- return (
229
- <button
230
- key={block.type}
231
- type="button"
232
- role="listitem"
233
- onClick={() => onBlockClick(block)}
234
- className="border-border hover:border-primary bg-card group focus:ring-ring flex cursor-pointer items-start gap-3 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
235
- >
236
- <div className="bg-accent text-foreground group-hover:bg-primary/10 group-hover:text-primary flex h-9 w-9 shrink-0 items-center justify-center rounded-md transition-colors">
237
- <Icon size={18} />
238
- </div>
239
- <div className="min-w-0">
240
- <p className="text-foreground truncate text-sm font-medium">{block.label}</p>
241
- {block.description && (
242
- <p className="text-muted-foreground mt-0.5 line-clamp-2 text-xs">
243
- {block.description}
244
- </p>
245
- )}
246
- </div>
247
- </button>
248
- )
249
- })}
250
- </div>
251
- )}
252
- </div>
253
- </div>
254
- )
255
- }
256
-
257
- interface VariantViewProps {
258
- block: BlockTypeDefinition
259
- variantListRef: React.RefObject<HTMLDivElement | null>
260
- onBack: () => void
261
- onVariantClick: (variant: VariantDefinition) => void
262
- onKeyDown: (e: React.KeyboardEvent) => void
263
- }
264
-
265
- function VariantView({
266
- block,
267
- variantListRef,
268
- onBack,
269
- onVariantClick,
270
- onKeyDown,
271
- }: VariantViewProps) {
272
- const Icon = resolveIcon(block.icon)
273
-
274
- return (
275
- <div className="flex h-full flex-col" onKeyDown={onKeyDown}>
276
- <div className="flex items-center gap-3 px-5 pt-5 pb-3">
277
- <button
278
- type="button"
279
- onClick={onBack}
280
- className="text-muted-foreground hover:text-foreground hover:bg-accent rounded-md p-1.5 transition-colors"
281
- aria-label="Back to block types"
282
- >
283
- <ArrowLeft size={18} />
284
- </button>
285
- <div className="flex min-w-0 items-center gap-2">
286
- <Icon size={18} className="text-foreground shrink-0" />
287
- <h2 className="text-foreground truncate text-lg font-medium">{block.label}</h2>
288
- </div>
289
- </div>
290
-
291
- {block.description && (
292
- <p className="text-muted-foreground px-5 pb-3 text-sm">{block.description}</p>
293
- )}
294
-
295
- <div className="flex-1 overflow-y-auto px-5 pb-5" ref={variantListRef}>
296
- <div className="space-y-2" role="list" aria-label={`${block.label} variants`}>
297
- {block.variants.map((variant, index) => (
298
- <button
299
- key={variant.name}
300
- type="button"
301
- role="listitem"
302
- data-variant={variant.name}
303
- onClick={() => onVariantClick(variant)}
304
- className="border-border hover:border-primary bg-card group focus:ring-ring flex w-full cursor-pointer items-center gap-3 rounded-lg border p-4 text-left transition-colors focus:ring-2 focus:outline-none"
305
- >
306
- <div className="min-w-0 flex-1">
307
- <div className="flex items-center gap-2">
308
- <p className="text-foreground text-sm font-medium">{variant.label}</p>
309
- {index === 0 && (
310
- <span className="text-primary bg-primary/10 inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs">
311
- <Check size={10} />
312
- Default
313
- </span>
314
- )}
315
- </div>
316
- <p className="text-muted-foreground mt-0.5 text-xs">{variant.description}</p>
317
- </div>
318
- </button>
319
- ))}
320
- </div>
321
- </div>
322
- </div>
323
- )
324
- }
@@ -1,65 +0,0 @@
1
- 'use client'
2
-
3
- import { Plus, Grid3X3 } from 'lucide-react'
4
- import { Toggle } from '../../components/ui/Toggle.js'
5
- import type { DeviceMode } from '../../hooks/useBuilderState.js'
6
-
7
- export interface BottomBarProps {
8
- deviceMode: DeviceMode
9
- showGridOverlay: boolean
10
- onAddSection: () => void
11
- onToggleGrid: (show: boolean) => void
12
- }
13
-
14
- const DEVICE_LABELS: Record<DeviceMode, string> = {
15
- desktop: '100%',
16
- tablet: '768px',
17
- mobile: '375px',
18
- }
19
-
20
- export function BottomBar({
21
- deviceMode,
22
- showGridOverlay,
23
- onAddSection,
24
- onToggleGrid,
25
- }: BottomBarProps) {
26
- return (
27
- <div
28
- className="bg-card border-border flex h-10 shrink-0 items-center gap-4 border-t px-4"
29
- role="toolbar"
30
- aria-label="Builder actions"
31
- >
32
- {/* Add Section */}
33
- <button
34
- onClick={onAddSection}
35
- className="text-muted-foreground hover:text-foreground flex items-center gap-1.5 px-3 py-2 text-sm transition-colors"
36
- aria-label="Add a new section"
37
- >
38
- <Plus size={14} />
39
- <span>Add Section</span>
40
- </button>
41
-
42
- {/* Divider */}
43
- <div className="bg-border h-5 w-px" />
44
-
45
- {/* Grid overlay toggle */}
46
- <label className="flex cursor-pointer items-center gap-2">
47
- <Grid3X3 size={14} className="text-muted-foreground" />
48
- <Toggle
49
- checked={showGridOverlay}
50
- onCheckedChange={onToggleGrid}
51
- aria-label="Toggle grid overlay"
52
- />
53
- <span className="text-muted-foreground text-xs">Grid</span>
54
- </label>
55
-
56
- {/* Spacer */}
57
- <div className="flex-1" />
58
-
59
- {/* Zoom / device width display */}
60
- <span className="text-muted-foreground text-xs tabular-nums">
61
- {DEVICE_LABELS[deviceMode]}
62
- </span>
63
- </div>
64
- )
65
- }
@@ -1,304 +0,0 @@
1
- 'use client'
2
-
3
- import { useMemo } from 'react'
4
- import {
5
- DndContext,
6
- KeyboardSensor,
7
- PointerSensor,
8
- closestCenter,
9
- useSensor,
10
- useSensors,
11
- type DragEndEvent,
12
- } from '@dnd-kit/core'
13
- import {
14
- SortableContext,
15
- sortableKeyboardCoordinates,
16
- useSortable,
17
- verticalListSortingStrategy,
18
- } from '@dnd-kit/sortable'
19
- import { CSS } from '@dnd-kit/utilities'
20
- import {
21
- Bookmark,
22
- Code,
23
- FileText,
24
- Grid,
25
- GripVertical,
26
- HelpCircle,
27
- Image,
28
- LayoutGrid,
29
- LayoutTemplate,
30
- MousePointer,
31
- PanelLeftClose,
32
- PanelLeftOpen,
33
- Play,
34
- Plus,
35
- Star,
36
- Type,
37
- type LucideIcon,
38
- } from 'lucide-react'
39
- import { BlockCatalog } from '@prenta/core/page-builder'
40
- import type { BuilderNode, PageNode } from '@prenta/core'
41
-
42
- export interface BuilderSectionsPanelProps {
43
- tree: PageNode
44
- selectedNodeId: string | null
45
- /** Collapsed to a slim rail. State is owned by PageBuilder (persisted). */
46
- collapsed: boolean
47
- onToggleCollapsed: () => void
48
- onSelect: (id: string) => void
49
- /** Move a top-level section to `toIndex` within the page. */
50
- onReorder: (id: string, toIndex: number) => void
51
- onAddSection: () => void
52
- }
53
-
54
- interface SectionEntry {
55
- id: string
56
- name: string
57
- meta: string
58
- icon: LucideIcon
59
- hidden: boolean
60
- }
61
-
62
- /** Same icon-name → component mapping the BlockPicker uses for the catalog. */
63
- const BLOCK_ICONS: Record<string, LucideIcon> = {
64
- Star,
65
- Type,
66
- Image,
67
- LayoutGrid,
68
- MousePointer,
69
- Play,
70
- Grid,
71
- HelpCircle,
72
- FileText,
73
- Code,
74
- }
75
-
76
- function collectBlockTypes(node: BuilderNode, out: string[]): void {
77
- if (node.type === 'block') {
78
- out.push(node.settings.blockType)
79
- return
80
- }
81
- if (node.type === 'savedSectionRef') return
82
- for (const child of node.children) collectBlockTypes(child, out)
83
- }
84
-
85
- function humanize(type: string): string {
86
- return type.charAt(0).toUpperCase() + type.slice(1)
87
- }
88
-
89
- /**
90
- * Builder sections have no user-facing title, so each row derives its name
91
- * from the section's first block (via the catalog label) and summarizes its
92
- * contents as the kind label — enough for the editor to recognize and jump
93
- * between sections without inventing data we don't store.
94
- */
95
- function buildEntries(tree: PageNode, catalog: BlockCatalog): SectionEntry[] {
96
- return tree.children.map((node, index) => {
97
- if (node.type === 'savedSectionRef') {
98
- return {
99
- id: node.id,
100
- name: 'Saved section',
101
- meta: 'From the section library',
102
- icon: Bookmark,
103
- hidden: false,
104
- }
105
- }
106
- const blockTypes: string[] = []
107
- collectBlockTypes(node, blockTypes)
108
- const firstType = blockTypes[0]
109
- const def = firstType ? catalog.get(firstType) : undefined
110
- return {
111
- id: node.id,
112
- name: def?.label ?? (firstType ? humanize(firstType) : `Section ${index + 1}`),
113
- meta:
114
- blockTypes.length === 0
115
- ? 'Empty section'
116
- : `${blockTypes.length} block${blockTypes.length === 1 ? '' : 's'}`,
117
- icon: (def && BLOCK_ICONS[def.icon]) ?? LayoutTemplate,
118
- hidden: node.settings.visibility === 'hidden',
119
- }
120
- })
121
- }
122
-
123
- const ICON_BUTTON_CLASS =
124
- 'flex h-8 w-8 items-center justify-center rounded-[7px] text-[var(--sub)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--acc-l)] hover:text-[var(--acc)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none'
125
-
126
- /**
127
- * Design-parity left rail for the Page Builder (PageBuilder.html): the page's
128
- * sections listed in order with drag-to-reorder, click-to-select (which
129
- * highlights the section on the canvas and opens its settings in the existing
130
- * right-hand context panel), and an "Add Section" affordance at the bottom.
131
- */
132
- export function BuilderSectionsPanel({
133
- tree,
134
- selectedNodeId,
135
- collapsed,
136
- onToggleCollapsed,
137
- onSelect,
138
- onReorder,
139
- onAddSection,
140
- }: BuilderSectionsPanelProps) {
141
- const catalog = useMemo(() => new BlockCatalog(), [])
142
- const entries = useMemo(() => buildEntries(tree, catalog), [tree, catalog])
143
-
144
- const sensors = useSensors(
145
- useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
146
- useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
147
- )
148
-
149
- function handleDragEnd(event: DragEndEvent) {
150
- const { active, over } = event
151
- if (!over || active.id === over.id) return
152
- const ids = entries.map((e) => e.id)
153
- const to = ids.indexOf(String(over.id))
154
- if (to === -1 || !ids.includes(String(active.id))) return
155
- onReorder(String(active.id), to)
156
- }
157
-
158
- if (collapsed) {
159
- return (
160
- <nav
161
- aria-label="Page sections"
162
- className="flex w-11 shrink-0 flex-col items-center border-r border-[var(--bdr)] bg-[var(--sb)] py-2"
163
- >
164
- <button
165
- type="button"
166
- onClick={onToggleCollapsed}
167
- aria-expanded={false}
168
- aria-label="Expand sections panel"
169
- className={ICON_BUTTON_CLASS}
170
- >
171
- <PanelLeftOpen size={16} aria-hidden />
172
- </button>
173
- </nav>
174
- )
175
- }
176
-
177
- return (
178
- <nav
179
- aria-label="Page sections"
180
- className="flex w-[280px] shrink-0 flex-col overflow-hidden border-r border-[var(--bdr)] bg-[var(--sb)]"
181
- >
182
- <div className="flex items-center justify-between border-b border-[var(--bdr)] py-2 pr-2 pl-3.5">
183
- <div className="flex items-center gap-2">
184
- <h2 className="text-[13px] font-medium text-[var(--txt)]">Sections</h2>
185
- <span className="rounded-[4px] bg-[var(--acc-l)] px-[7px] py-0.5 text-[11px] font-medium text-[var(--acc)] tabular-nums">
186
- {entries.length}
187
- </span>
188
- </div>
189
- <button
190
- type="button"
191
- onClick={onToggleCollapsed}
192
- aria-expanded
193
- aria-label="Collapse sections panel"
194
- className={ICON_BUTTON_CLASS}
195
- >
196
- <PanelLeftClose size={16} aria-hidden />
197
- </button>
198
- </div>
199
-
200
- <div className="flex-1 overflow-y-auto">
201
- {entries.length === 0 ? (
202
- <p className="px-4 py-6 text-center text-[12.5px] text-[var(--muted)]">
203
- No sections yet. Add one below to start building.
204
- </p>
205
- ) : (
206
- <DndContext
207
- sensors={sensors}
208
- collisionDetection={closestCenter}
209
- onDragEnd={handleDragEnd}
210
- >
211
- <SortableContext
212
- items={entries.map((e) => e.id)}
213
- strategy={verticalListSortingStrategy}
214
- >
215
- <ul aria-label="Sections in page order">
216
- {entries.map((entry) => (
217
- <SortableSectionRow
218
- key={entry.id}
219
- entry={entry}
220
- selected={entry.id === selectedNodeId}
221
- onSelect={onSelect}
222
- />
223
- ))}
224
- </ul>
225
- </SortableContext>
226
- </DndContext>
227
- )}
228
- </div>
229
-
230
- <div className="border-t border-[var(--bdr)]">
231
- <button
232
- type="button"
233
- onClick={onAddSection}
234
- className="flex w-full items-center gap-2 px-3.5 py-3 text-[13px] font-medium text-[var(--acc)] transition-colors duration-[var(--motion-fast)] hover:bg-[var(--acc-l)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none focus-visible:ring-inset"
235
- >
236
- <Plus size={14} aria-hidden />
237
- Add Section
238
- </button>
239
- </div>
240
- </nav>
241
- )
242
- }
243
-
244
- interface SortableSectionRowProps {
245
- entry: SectionEntry
246
- selected: boolean
247
- onSelect: (id: string) => void
248
- }
249
-
250
- function SortableSectionRow({ entry, selected, onSelect }: SortableSectionRowProps) {
251
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
252
- id: entry.id,
253
- })
254
- const Icon = entry.icon
255
-
256
- const style = {
257
- transform: CSS.Transform.toString(transform),
258
- transition,
259
- zIndex: isDragging ? 10 : undefined,
260
- }
261
-
262
- return (
263
- <li ref={setNodeRef} style={style} className={isDragging ? 'shadow-[var(--shd2)]' : undefined}>
264
- <div
265
- className={`flex items-center gap-1.5 border-b border-[var(--bdr)] py-2 pr-2.5 pl-1.5 transition-colors duration-[var(--motion-fast)] ${
266
- selected ? 'bg-[var(--acc-l)]' : 'hover:bg-[var(--acc-l)]'
267
- }`}
268
- >
269
- <button
270
- type="button"
271
- aria-label={`Drag to reorder ${entry.name}`}
272
- {...attributes}
273
- {...listeners}
274
- className="shrink-0 cursor-grab touch-none rounded p-1 text-[var(--muted)] transition-colors duration-[var(--motion-fast)] hover:text-[var(--acc)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none active:cursor-grabbing"
275
- >
276
- <GripVertical size={15} aria-hidden />
277
- </button>
278
- <button
279
- type="button"
280
- onClick={() => onSelect(entry.id)}
281
- aria-current={selected ? 'true' : undefined}
282
- className="flex min-w-0 flex-1 items-center gap-2.5 rounded text-left focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none"
283
- >
284
- <span
285
- aria-hidden
286
- className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[7px] bg-[var(--acc-l)] text-[var(--acc)]"
287
- >
288
- <Icon size={14} />
289
- </span>
290
- <span className="flex min-w-0 flex-col">
291
- <span
292
- className={`truncate text-[13px] font-medium ${
293
- entry.hidden ? 'text-[var(--muted)] line-through' : 'text-[var(--txt)]'
294
- }`}
295
- >
296
- {entry.name}
297
- </span>
298
- <span className="truncate text-[11px] text-[var(--muted)]">{entry.meta}</span>
299
- </span>
300
- </button>
301
- </div>
302
- </li>
303
- )
304
- }