@actuate-media/cms-admin 0.14.1 → 0.16.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 (304) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +148 -2
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/components/posts-badges.test.d.ts +2 -0
  5. package/dist/__tests__/components/posts-badges.test.d.ts.map +1 -0
  6. package/dist/__tests__/components/posts-badges.test.js +68 -0
  7. package/dist/__tests__/components/posts-badges.test.js.map +1 -0
  8. package/dist/__tests__/layout/sidebar-posts.test.d.ts +2 -0
  9. package/dist/__tests__/layout/sidebar-posts.test.d.ts.map +1 -0
  10. package/dist/__tests__/layout/sidebar-posts.test.js +162 -0
  11. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -0
  12. package/dist/__tests__/lib/page-editor-service.test.d.ts +2 -0
  13. package/dist/__tests__/lib/page-editor-service.test.d.ts.map +1 -0
  14. package/dist/__tests__/lib/page-editor-service.test.js +137 -0
  15. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -0
  16. package/dist/__tests__/lib/pages-service.test.d.ts +2 -0
  17. package/dist/__tests__/lib/pages-service.test.d.ts.map +1 -0
  18. package/dist/__tests__/lib/pages-service.test.js +140 -0
  19. package/dist/__tests__/lib/pages-service.test.js.map +1 -0
  20. package/dist/__tests__/lib/post-editor-service.test.d.ts +2 -0
  21. package/dist/__tests__/lib/post-editor-service.test.d.ts.map +1 -0
  22. package/dist/__tests__/lib/post-editor-service.test.js +185 -0
  23. package/dist/__tests__/lib/post-editor-service.test.js.map +1 -0
  24. package/dist/__tests__/lib/posts-service.test.d.ts +2 -0
  25. package/dist/__tests__/lib/posts-service.test.d.ts.map +1 -0
  26. package/dist/__tests__/lib/posts-service.test.js +244 -0
  27. package/dist/__tests__/lib/posts-service.test.js.map +1 -0
  28. package/dist/__tests__/views/posts-list-view.test.d.ts +2 -0
  29. package/dist/__tests__/views/posts-list-view.test.d.ts.map +1 -0
  30. package/dist/__tests__/views/posts-list-view.test.js +98 -0
  31. package/dist/__tests__/views/posts-list-view.test.js.map +1 -0
  32. package/dist/actuate-admin.css +1 -1
  33. package/dist/components/pages/tag-badges.d.ts +21 -0
  34. package/dist/components/pages/tag-badges.d.ts.map +1 -0
  35. package/dist/components/pages/tag-badges.js +56 -0
  36. package/dist/components/pages/tag-badges.js.map +1 -0
  37. package/dist/components/posts/badges.d.ts +25 -0
  38. package/dist/components/posts/badges.d.ts.map +1 -0
  39. package/dist/components/posts/badges.js +123 -0
  40. package/dist/components/posts/badges.js.map +1 -0
  41. package/dist/components/posts/icons.d.ts +12 -0
  42. package/dist/components/posts/icons.d.ts.map +1 -0
  43. package/dist/components/posts/icons.js +26 -0
  44. package/dist/components/posts/icons.js.map +1 -0
  45. package/dist/layout/Header.d.ts +14 -1
  46. package/dist/layout/Header.d.ts.map +1 -1
  47. package/dist/layout/Header.js +10 -3
  48. package/dist/layout/Header.js.map +1 -1
  49. package/dist/layout/Layout.d.ts.map +1 -1
  50. package/dist/layout/Layout.js +78 -2
  51. package/dist/layout/Layout.js.map +1 -1
  52. package/dist/layout/Sidebar.d.ts +16 -0
  53. package/dist/layout/Sidebar.d.ts.map +1 -1
  54. package/dist/layout/Sidebar.js +176 -49
  55. package/dist/layout/Sidebar.js.map +1 -1
  56. package/dist/lib/page-editor-service.d.ts +45 -0
  57. package/dist/lib/page-editor-service.d.ts.map +1 -0
  58. package/dist/lib/page-editor-service.js +153 -0
  59. package/dist/lib/page-editor-service.js.map +1 -0
  60. package/dist/lib/pages-service.d.ts +143 -0
  61. package/dist/lib/pages-service.d.ts.map +1 -0
  62. package/dist/lib/pages-service.js +444 -0
  63. package/dist/lib/pages-service.js.map +1 -0
  64. package/dist/lib/post-editor-service.d.ts +57 -0
  65. package/dist/lib/post-editor-service.d.ts.map +1 -0
  66. package/dist/lib/post-editor-service.js +234 -0
  67. package/dist/lib/post-editor-service.js.map +1 -0
  68. package/dist/lib/posts-service.d.ts +73 -0
  69. package/dist/lib/posts-service.d.ts.map +1 -0
  70. package/dist/lib/posts-service.js +245 -0
  71. package/dist/lib/posts-service.js.map +1 -0
  72. package/dist/lib/preview-link.d.ts +32 -0
  73. package/dist/lib/preview-link.d.ts.map +1 -0
  74. package/dist/lib/preview-link.js +40 -0
  75. package/dist/lib/preview-link.js.map +1 -0
  76. package/dist/router/index.d.ts +1 -0
  77. package/dist/router/index.d.ts.map +1 -1
  78. package/dist/router/index.js +9 -1
  79. package/dist/router/index.js.map +1 -1
  80. package/dist/views/Dashboard.d.ts.map +1 -1
  81. package/dist/views/Dashboard.js +147 -62
  82. package/dist/views/Dashboard.js.map +1 -1
  83. package/dist/views/Pages/PageTagManager.d.ts +14 -0
  84. package/dist/views/Pages/PageTagManager.d.ts.map +1 -0
  85. package/dist/views/Pages/PageTagManager.js +145 -0
  86. package/dist/views/Pages/PageTagManager.js.map +1 -0
  87. package/dist/views/Pages/PagesListView.d.ts +18 -0
  88. package/dist/views/Pages/PagesListView.d.ts.map +1 -0
  89. package/dist/views/Pages/PagesListView.js +287 -0
  90. package/dist/views/Pages/PagesListView.js.map +1 -0
  91. package/dist/views/Posts/NewPostPicker.d.ts +16 -0
  92. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -0
  93. package/dist/views/Posts/NewPostPicker.js +59 -0
  94. package/dist/views/Posts/NewPostPicker.js.map +1 -0
  95. package/dist/views/Posts/NewPostTypeInstructions.d.ts +18 -0
  96. package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -0
  97. package/dist/views/Posts/NewPostTypeInstructions.js +147 -0
  98. package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -0
  99. package/dist/views/Posts/PostTypesView.d.ts +26 -0
  100. package/dist/views/Posts/PostTypesView.d.ts.map +1 -0
  101. package/dist/views/Posts/PostTypesView.js +52 -0
  102. package/dist/views/Posts/PostTypesView.js.map +1 -0
  103. package/dist/views/Posts/PostsListView.d.ts +25 -0
  104. package/dist/views/Posts/PostsListView.d.ts.map +1 -0
  105. package/dist/views/Posts/PostsListView.js +239 -0
  106. package/dist/views/Posts/PostsListView.js.map +1 -0
  107. package/dist/views/Posts/post-type-presets.d.ts +32 -0
  108. package/dist/views/Posts/post-type-presets.d.ts.map +1 -0
  109. package/dist/views/Posts/post-type-presets.js +72 -0
  110. package/dist/views/Posts/post-type-presets.js.map +1 -0
  111. package/dist/views/page-builder/Widgets.d.ts +13 -0
  112. package/dist/views/page-builder/Widgets.d.ts.map +1 -0
  113. package/dist/views/page-builder/Widgets.js +15 -0
  114. package/dist/views/page-builder/Widgets.js.map +1 -0
  115. package/dist/views/page-editor/AddSectionModal.d.ts +20 -0
  116. package/dist/views/page-editor/AddSectionModal.d.ts.map +1 -0
  117. package/dist/views/page-editor/AddSectionModal.js +22 -0
  118. package/dist/views/page-editor/AddSectionModal.js.map +1 -0
  119. package/dist/views/page-editor/EditorCanvas.d.ts +18 -0
  120. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -0
  121. package/dist/views/page-editor/EditorCanvas.js +64 -0
  122. package/dist/views/page-editor/EditorCanvas.js.map +1 -0
  123. package/dist/views/page-editor/EditorTopBar.d.ts +30 -0
  124. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -0
  125. package/dist/views/page-editor/EditorTopBar.js +42 -0
  126. package/dist/views/page-editor/EditorTopBar.js.map +1 -0
  127. package/dist/views/page-editor/PagePreview.d.ts +13 -0
  128. package/dist/views/page-editor/PagePreview.d.ts.map +1 -0
  129. package/dist/views/page-editor/PagePreview.js +37 -0
  130. package/dist/views/page-editor/PagePreview.js.map +1 -0
  131. package/dist/views/page-editor/PageSectionEditor.d.ts +16 -0
  132. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -0
  133. package/dist/views/page-editor/PageSectionEditor.js +272 -0
  134. package/dist/views/page-editor/PageSectionEditor.js.map +1 -0
  135. package/dist/views/page-editor/PageSettingsModal.d.ts +12 -0
  136. package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -0
  137. package/dist/views/page-editor/PageSettingsModal.js +52 -0
  138. package/dist/views/page-editor/PageSettingsModal.js.map +1 -0
  139. package/dist/views/page-editor/SectionInspector.d.ts +17 -0
  140. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -0
  141. package/dist/views/page-editor/SectionInspector.js +98 -0
  142. package/dist/views/page-editor/SectionInspector.js.map +1 -0
  143. package/dist/views/page-editor/SectionsPanel.d.ts +15 -0
  144. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -0
  145. package/dist/views/page-editor/SectionsPanel.js +43 -0
  146. package/dist/views/page-editor/SectionsPanel.js.map +1 -0
  147. package/dist/views/page-editor/ValidationSummary.d.ts +9 -0
  148. package/dist/views/page-editor/ValidationSummary.d.ts.map +1 -0
  149. package/dist/views/page-editor/ValidationSummary.js +10 -0
  150. package/dist/views/page-editor/ValidationSummary.js.map +1 -0
  151. package/dist/views/page-editor/section-icons.d.ts +4 -0
  152. package/dist/views/page-editor/section-icons.d.ts.map +1 -0
  153. package/dist/views/page-editor/section-icons.js +20 -0
  154. package/dist/views/page-editor/section-icons.js.map +1 -0
  155. package/dist/views/page-editor/section-types.d.ts +11 -0
  156. package/dist/views/page-editor/section-types.d.ts.map +1 -0
  157. package/dist/views/page-editor/section-types.js +10 -0
  158. package/dist/views/page-editor/section-types.js.map +1 -0
  159. package/dist/views/page-editor/sections/ArticleBodySection.d.ts +13 -0
  160. package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -0
  161. package/dist/views/page-editor/sections/ArticleBodySection.js +23 -0
  162. package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -0
  163. package/dist/views/page-editor/sections/AuthorBioSection.d.ts +8 -0
  164. package/dist/views/page-editor/sections/AuthorBioSection.d.ts.map +1 -0
  165. package/dist/views/page-editor/sections/AuthorBioSection.js +21 -0
  166. package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -0
  167. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts +5 -0
  168. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -0
  169. package/dist/views/page-editor/sections/ByTheNumbersSection.js +10 -0
  170. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -0
  171. package/dist/views/page-editor/sections/CtaBannerSection.d.ts +5 -0
  172. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -0
  173. package/dist/views/page-editor/sections/CtaBannerSection.js +10 -0
  174. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -0
  175. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts +5 -0
  176. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -0
  177. package/dist/views/page-editor/sections/FeatureHighlightSection.js +18 -0
  178. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -0
  179. package/dist/views/page-editor/sections/GallerySection.d.ts +6 -0
  180. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -0
  181. package/dist/views/page-editor/sections/GallerySection.js +17 -0
  182. package/dist/views/page-editor/sections/GallerySection.js.map +1 -0
  183. package/dist/views/page-editor/sections/HeroBannerSection.d.ts +5 -0
  184. package/dist/views/page-editor/sections/HeroBannerSection.d.ts.map +1 -0
  185. package/dist/views/page-editor/sections/HeroBannerSection.js +11 -0
  186. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -0
  187. package/dist/views/page-editor/sections/MissionSection.d.ts +6 -0
  188. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -0
  189. package/dist/views/page-editor/sections/MissionSection.js +12 -0
  190. package/dist/views/page-editor/sections/MissionSection.js.map +1 -0
  191. package/dist/views/page-editor/sections/PullQuoteSection.d.ts +6 -0
  192. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -0
  193. package/dist/views/page-editor/sections/PullQuoteSection.js +13 -0
  194. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -0
  195. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts +12 -0
  196. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -0
  197. package/dist/views/page-editor/sections/RelatedPostsSection.js +23 -0
  198. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -0
  199. package/dist/views/page-editor/sections/SectionRenderer.d.ts +22 -0
  200. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -0
  201. package/dist/views/page-editor/sections/SectionRenderer.js +52 -0
  202. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -0
  203. package/dist/views/page-editor/sections/index.d.ts +14 -0
  204. package/dist/views/page-editor/sections/index.d.ts.map +1 -0
  205. package/dist/views/page-editor/sections/index.js +12 -0
  206. package/dist/views/page-editor/sections/index.js.map +1 -0
  207. package/dist/views/page-editor/sections/parts.d.ts +55 -0
  208. package/dist/views/page-editor/sections/parts.d.ts.map +1 -0
  209. package/dist/views/page-editor/sections/parts.js +60 -0
  210. package/dist/views/page-editor/sections/parts.js.map +1 -0
  211. package/dist/views/page-editor/viewports.d.ts +14 -0
  212. package/dist/views/page-editor/viewports.d.ts.map +1 -0
  213. package/dist/views/page-editor/viewports.js +12 -0
  214. package/dist/views/page-editor/viewports.js.map +1 -0
  215. package/dist/views/post-editor/PostEditorCanvas.d.ts +21 -0
  216. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -0
  217. package/dist/views/post-editor/PostEditorCanvas.js +58 -0
  218. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -0
  219. package/dist/views/post-editor/PostFieldsModal.d.ts +12 -0
  220. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -0
  221. package/dist/views/post-editor/PostFieldsModal.js +53 -0
  222. package/dist/views/post-editor/PostFieldsModal.js.map +1 -0
  223. package/dist/views/post-editor/PostHeader.d.ts +15 -0
  224. package/dist/views/post-editor/PostHeader.d.ts.map +1 -0
  225. package/dist/views/post-editor/PostHeader.js +27 -0
  226. package/dist/views/post-editor/PostHeader.js.map +1 -0
  227. package/dist/views/post-editor/PostHeaderPanel.d.ts +11 -0
  228. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -0
  229. package/dist/views/post-editor/PostHeaderPanel.js +21 -0
  230. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -0
  231. package/dist/views/post-editor/PostPreview.d.ts +14 -0
  232. package/dist/views/post-editor/PostPreview.d.ts.map +1 -0
  233. package/dist/views/post-editor/PostPreview.js +53 -0
  234. package/dist/views/post-editor/PostPreview.js.map +1 -0
  235. package/dist/views/post-editor/PostSectionEditor.d.ts +17 -0
  236. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -0
  237. package/dist/views/post-editor/PostSectionEditor.js +290 -0
  238. package/dist/views/post-editor/PostSectionEditor.js.map +1 -0
  239. package/dist/views/post-editor/PostTemplateEditor.d.ts +9 -0
  240. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -0
  241. package/dist/views/post-editor/PostTemplateEditor.js +144 -0
  242. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -0
  243. package/package.json +2 -2
  244. package/src/AdminRoot.tsx +213 -2
  245. package/src/__tests__/components/posts-badges.test.tsx +77 -0
  246. package/src/__tests__/layout/sidebar-posts.test.ts +179 -0
  247. package/src/__tests__/lib/page-editor-service.test.ts +173 -0
  248. package/src/__tests__/lib/pages-service.test.ts +157 -0
  249. package/src/__tests__/lib/post-editor-service.test.ts +215 -0
  250. package/src/__tests__/lib/posts-service.test.ts +279 -0
  251. package/src/__tests__/views/posts-list-view.test.ts +110 -0
  252. package/src/components/pages/tag-badges.tsx +104 -0
  253. package/src/components/posts/badges.tsx +170 -0
  254. package/src/components/posts/icons.ts +40 -0
  255. package/src/layout/Header.tsx +90 -43
  256. package/src/layout/Layout.tsx +166 -1
  257. package/src/layout/Sidebar.tsx +287 -101
  258. package/src/lib/page-editor-service.ts +233 -0
  259. package/src/lib/pages-service.ts +599 -0
  260. package/src/lib/post-editor-service.ts +342 -0
  261. package/src/lib/posts-service.ts +366 -0
  262. package/src/lib/preview-link.ts +63 -0
  263. package/src/router/index.ts +10 -1
  264. package/src/views/Dashboard.tsx +270 -131
  265. package/src/views/Pages/PageTagManager.tsx +436 -0
  266. package/src/views/Pages/PagesListView.tsx +944 -0
  267. package/src/views/Posts/NewPostPicker.tsx +147 -0
  268. package/src/views/Posts/NewPostTypeInstructions.tsx +344 -0
  269. package/src/views/Posts/PostTypesView.tsx +194 -0
  270. package/src/views/Posts/PostsListView.tsx +869 -0
  271. package/src/views/Posts/post-type-presets.ts +105 -0
  272. package/src/views/page-builder/Widgets.tsx +44 -0
  273. package/src/views/page-editor/AddSectionModal.tsx +56 -0
  274. package/src/views/page-editor/EditorCanvas.tsx +127 -0
  275. package/src/views/page-editor/EditorTopBar.tsx +233 -0
  276. package/src/views/page-editor/PagePreview.tsx +96 -0
  277. package/src/views/page-editor/PageSectionEditor.tsx +476 -0
  278. package/src/views/page-editor/PageSettingsModal.tsx +154 -0
  279. package/src/views/page-editor/SectionInspector.tsx +599 -0
  280. package/src/views/page-editor/SectionsPanel.tsx +270 -0
  281. package/src/views/page-editor/ValidationSummary.tsx +53 -0
  282. package/src/views/page-editor/section-icons.ts +33 -0
  283. package/src/views/page-editor/section-types.ts +27 -0
  284. package/src/views/page-editor/sections/ArticleBodySection.tsx +57 -0
  285. package/src/views/page-editor/sections/AuthorBioSection.tsx +61 -0
  286. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +41 -0
  287. package/src/views/page-editor/sections/CtaBannerSection.tsx +29 -0
  288. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +63 -0
  289. package/src/views/page-editor/sections/GallerySection.tsx +54 -0
  290. package/src/views/page-editor/sections/HeroBannerSection.tsx +44 -0
  291. package/src/views/page-editor/sections/MissionSection.tsx +22 -0
  292. package/src/views/page-editor/sections/PullQuoteSection.tsx +27 -0
  293. package/src/views/page-editor/sections/RelatedPostsSection.tsx +55 -0
  294. package/src/views/page-editor/sections/SectionRenderer.tsx +102 -0
  295. package/src/views/page-editor/sections/index.ts +13 -0
  296. package/src/views/page-editor/sections/parts.tsx +113 -0
  297. package/src/views/page-editor/viewports.ts +25 -0
  298. package/src/views/post-editor/PostEditorCanvas.tsx +123 -0
  299. package/src/views/post-editor/PostFieldsModal.tsx +196 -0
  300. package/src/views/post-editor/PostHeader.tsx +87 -0
  301. package/src/views/post-editor/PostHeaderPanel.tsx +107 -0
  302. package/src/views/post-editor/PostPreview.tsx +114 -0
  303. package/src/views/post-editor/PostSectionEditor.tsx +506 -0
  304. package/src/views/post-editor/PostTemplateEditor.tsx +347 -0
@@ -1,5 +1,6 @@
1
1
  'use client'
2
2
 
3
+ import type { ReactNode } from 'react'
3
4
  import {
4
5
  LayoutDashboard,
5
6
  FileText,
@@ -7,7 +8,6 @@ import {
7
8
  Image,
8
9
  Settings,
9
10
  ChevronLeft,
10
- ChevronRight,
11
11
  ClipboardList,
12
12
  Users,
13
13
  Search as SearchIcon,
@@ -19,10 +19,14 @@ import {
19
19
  PanelTop,
20
20
  PanelBottom,
21
21
  Layers,
22
+ LayoutGrid,
22
23
  Code2,
23
- LayoutTemplate,
24
24
  Library,
25
25
  KeyRound,
26
+ ListTree,
27
+ Menu as MenuIcon,
28
+ PanelLeftClose,
29
+ PanelLeftOpen,
26
30
  } from 'lucide-react'
27
31
  import type { LucideIcon } from 'lucide-react'
28
32
  import { ActuateBrandLogo } from '../assets/actuate-logo.js'
@@ -48,11 +52,6 @@ function ActuateMark({ className }: { className?: string }) {
48
52
  )
49
53
  }
50
54
 
51
- /**
52
- * Full Actuate Media lockup. Inline SVG with a transparent background so it
53
- * sits naturally on whatever surface the sidebar uses (light or dark theme,
54
- * custom branding background, etc.).
55
- */
56
55
  function ActuateWordmark({ className }: { className?: string }) {
57
56
  return <ActuateBrandLogo className={className} />
58
57
  }
@@ -65,6 +64,9 @@ const ICON_MAP: Record<string, LucideIcon> = {
65
64
  book: BookOpen,
66
65
  help: HelpCircle,
67
66
  newspaper: Newspaper,
67
+ navigation: ListTree,
68
+ menu: MenuIcon,
69
+ 'list-tree': ListTree,
68
70
  PanelTop: PanelTop,
69
71
  PanelBottom: PanelBottom,
70
72
  Layers: Layers,
@@ -91,9 +93,7 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
91
93
  className="h-8 w-auto shrink-0 object-contain"
92
94
  />
93
95
  {brandName && (
94
- <span className="text-sidebar-foreground truncate text-sm font-semibold">
95
- {brandName}
96
- </span>
96
+ <span className="text-sidebar-foreground truncate text-sm font-medium">{brandName}</span>
97
97
  )}
98
98
  </div>
99
99
  )
@@ -103,7 +103,7 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
103
103
  return (
104
104
  <div className="flex min-w-0 items-center gap-2.5">
105
105
  <ActuateMark className="h-7 w-7 shrink-0" />
106
- <span className="text-sidebar-foreground truncate text-sm font-semibold">{brandName}</span>
106
+ <span className="text-sidebar-foreground truncate text-sm font-medium">{brandName}</span>
107
107
  </div>
108
108
  )
109
109
  }
@@ -111,16 +111,54 @@ function BrandLogo({ config, collapsed }: { config?: any; collapsed: boolean })
111
111
  return <ActuateWordmark className="h-9" />
112
112
  }
113
113
 
114
- const defaultNavItems = [
114
+ /**
115
+ * Children of the Posts parent. Same shape regardless of how many
116
+ * post-type collections are registered — Post Types are accessed via
117
+ * the `/posts/types` cards page, not as individual sidebar entries.
118
+ */
119
+ const POSTS_CHILDREN: NavItem[] = [
120
+ { path: '/posts', label: 'All Posts', icon: FileText },
121
+ { path: '/posts/types', label: 'Post Types', icon: Layers },
122
+ ]
123
+
124
+ /**
125
+ * Children of the Templates parent. Templates is its own top-level item
126
+ * (page building tools) — Saved Sections are reusable content blocks and
127
+ * Widgets are embeddable components. These are deliberately NOT children
128
+ * of Pages: Pages is content, Templates/Sections/Widgets are structure.
129
+ */
130
+ const TEMPLATES_CHILDREN: NavItem[] = [
131
+ { path: '/saved-sections', label: 'Saved Sections', icon: Library },
132
+ { path: '/widgets', label: 'Widgets', icon: LayoutGrid },
133
+ ]
134
+
135
+ /**
136
+ * Nav tree for the default (config-less) install. Dashboard sits on its
137
+ * own at the top, then a CONTENT group holding Pages (no children) above
138
+ * Posts (with All Posts + Post Types) and Templates (with Saved Sections
139
+ * + Widgets), followed by Media / Forms / SEO / Script Tags, then a
140
+ * SETTINGS group (Users / API Keys / Settings). The group ordering is
141
+ * meaningful — the renderer emits a section header the first time it
142
+ * sees a new group, so "CONTENT" renders once, above Pages.
143
+ */
144
+ const defaultNavItems: NavItem[] = [
115
145
  { path: '/', label: 'Dashboard', icon: LayoutDashboard },
116
- { path: '/pages', label: 'Pages', icon: File },
117
- { path: '/posts', label: 'Posts', icon: FileText },
118
- { path: '/media', label: 'Media', icon: Image },
119
- { path: '/forms', label: 'Forms', icon: ClipboardList },
120
- { path: '/seo', label: 'SEO', icon: SearchIcon },
121
- { path: '/users', label: 'Users', icon: Users },
122
- { path: '/api-keys', label: 'API Keys', icon: KeyRound },
123
- { path: '/settings', label: 'Settings', icon: Settings },
146
+ { path: '/pages', label: 'Pages', icon: File, group: 'Content' },
147
+ { path: '/posts', label: 'Posts', icon: FileText, group: 'Content', children: POSTS_CHILDREN },
148
+ {
149
+ path: '/page-templates',
150
+ label: 'Templates',
151
+ icon: Layers,
152
+ group: 'Content',
153
+ children: TEMPLATES_CHILDREN,
154
+ },
155
+ { path: '/media', label: 'Media', icon: Image, group: 'Content' },
156
+ { path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' },
157
+ { path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' },
158
+ { path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' },
159
+ { path: '/users', label: 'Users', icon: Users, group: 'Settings' },
160
+ { path: '/api-keys', label: 'API Keys', icon: KeyRound, group: 'Settings' },
161
+ { path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' },
124
162
  ]
125
163
 
126
164
  export interface SidebarProps {
@@ -142,73 +180,155 @@ export function Sidebar({
142
180
 
143
181
  return (
144
182
  <aside
145
- className={`bg-sidebar border-sidebar-border h-full border-r transition-all duration-200 ${
183
+ className={`bg-sidebar border-sidebar-border flex h-full flex-col border-r transition-all duration-200 ${
146
184
  collapsed ? 'w-20' : 'w-64'
147
185
  }`}
148
186
  >
187
+ {/* Brand header — no toggle button here anymore; it now lives in the footer. */}
149
188
  <div
150
- className={`border-sidebar-border flex h-14 items-center border-b px-4 ${
151
- collapsed ? 'justify-center' : 'justify-between'
189
+ className={`border-sidebar-border flex h-14 shrink-0 items-center border-b px-4 ${
190
+ collapsed ? 'justify-center' : ''
152
191
  }`}
153
192
  >
154
- {!collapsed && <BrandLogo config={config} collapsed={false} />}
155
- {collapsed && <BrandLogo config={config} collapsed={true} />}
193
+ <BrandLogo config={config} collapsed={collapsed} />
194
+ </div>
195
+
196
+ <nav className="flex-1 space-y-1 overflow-y-auto p-3" aria-label="Primary">
197
+ {renderNavTree(navItems, { collapsed, currentPath, onNavigate })}
198
+ </nav>
199
+
200
+ {/* Footer collapse control. Sits at the bottom of the sidebar so it
201
+ mirrors the reference design and stays out of the way during
202
+ everyday nav. Hidden below md: the mobile sheet has its own
203
+ dismiss affordance in <AdminShell>. */}
204
+ <div className="border-sidebar-border hidden shrink-0 border-t p-3 md:block">
156
205
  <button
157
206
  onClick={onToggleCollapse}
158
- className="hover:bg-sidebar-accent hidden shrink-0 rounded-lg p-2 transition-colors md:block"
207
+ className={`text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-foreground flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors ${
208
+ collapsed ? 'justify-center' : ''
209
+ }`}
159
210
  aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
211
+ title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
160
212
  >
161
213
  {collapsed ? (
162
- <ChevronRight className="text-sidebar-foreground h-4 w-4" />
214
+ <PanelLeftOpen className="h-4 w-4 shrink-0" aria-hidden />
163
215
  ) : (
164
- <ChevronLeft className="text-sidebar-foreground h-4 w-4" />
216
+ <>
217
+ <PanelLeftClose className="h-4 w-4 shrink-0" aria-hidden />
218
+ <span className="font-medium">Collapse sidebar</span>
219
+ </>
165
220
  )}
166
221
  </button>
167
222
  </div>
168
-
169
- <nav className="space-y-1 p-3">
170
- {navItems.map((item, idx) => {
171
- const Icon = item.icon
172
- const isActive =
173
- currentPath === item.path || (item.path !== '/' && currentPath.startsWith(item.path))
174
-
175
- const prevGroup = idx > 0 ? navItems[idx - 1]?.group : undefined
176
- const showGroupLabel = item.group && item.group !== prevGroup
177
-
178
- return (
179
- <div key={item.path}>
180
- {showGroupLabel && !collapsed && (
181
- <div className="px-3 pt-3 pb-1">
182
- <span className="text-sidebar-foreground/50 text-[10px] font-semibold tracking-wider uppercase">
183
- {item.group}
184
- </span>
185
- </div>
186
- )}
187
- {showGroupLabel && collapsed && (
188
- <div className="flex justify-center pt-2 pb-1">
189
- <span className="border-sidebar-foreground/20 w-4 border-t" />
190
- </div>
191
- )}
192
- <button
193
- onClick={() => onNavigate(item.path)}
194
- className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-colors ${
195
- isActive
196
- ? 'bg-sidebar-accent text-sidebar-primary'
197
- : 'text-sidebar-foreground hover:bg-sidebar-accent'
198
- } ${collapsed ? 'justify-center' : ''}`}
199
- title={collapsed ? item.label : ''}
200
- >
201
- <Icon className="h-5 w-5 shrink-0" />
202
- {!collapsed && <span className="text-sm font-medium">{item.label}</span>}
203
- </button>
204
- </div>
205
- )
206
- })}
207
- </nav>
208
223
  </aside>
209
224
  )
210
225
  }
211
226
 
227
+ // ─── Rendering ──────────────────────────────────────────────────────────────
228
+
229
+ interface NavRenderContext {
230
+ collapsed: boolean
231
+ currentPath: string
232
+ onNavigate: (path: string) => void
233
+ }
234
+
235
+ function isPathActive(currentPath: string, itemPath: string): boolean {
236
+ if (itemPath === '/') return currentPath === '/'
237
+
238
+ // Special case: /posts is a prefix of /posts/types — without this
239
+ // guard, "All Posts" (/posts) would stay highlighted while the user
240
+ // is on /posts/types or /posts/types/new. Treat /posts as exact-only
241
+ // when a sibling /posts/* route exists.
242
+ if (itemPath === '/posts') {
243
+ return currentPath === '/posts' || currentPath === '/posts/new'
244
+ }
245
+
246
+ // Use boundary match so /pages doesn't match /pages-something. Exact, or
247
+ // followed by a `/` separator.
248
+ return currentPath === itemPath || currentPath.startsWith(itemPath + '/')
249
+ }
250
+
251
+ function renderNavTree(items: NavItem[], ctx: NavRenderContext): ReactNode {
252
+ const nodes: ReactNode[] = []
253
+ let lastGroup: string | undefined = undefined
254
+
255
+ for (const item of items) {
256
+ if (item.group && item.group !== lastGroup) {
257
+ nodes.push(
258
+ ctx.collapsed ? (
259
+ <div key={`g-${item.group}`} className="flex justify-center pt-3 pb-1">
260
+ <span className="border-sidebar-foreground/20 w-4 border-t" aria-hidden />
261
+ </div>
262
+ ) : (
263
+ <div key={`g-${item.group}`} className="px-3 pt-4 pb-1">
264
+ <span className="text-sidebar-foreground/55 text-[10px] font-semibold tracking-wider uppercase">
265
+ {item.group}
266
+ </span>
267
+ </div>
268
+ ),
269
+ )
270
+ lastGroup = item.group
271
+ } else if (!item.group) {
272
+ lastGroup = undefined
273
+ }
274
+
275
+ // Keys are namespaced because a parent and its first child can share
276
+ // the same path (e.g. the Posts parent and its "All Posts" child are
277
+ // both `/posts`). Using the raw path as the key collided and made
278
+ // React warn about — and risk dropping — duplicate siblings.
279
+ nodes.push(<NavLink key={`item:${item.path}`} item={item} ctx={ctx} />)
280
+ if (item.children && item.children.length > 0 && !ctx.collapsed) {
281
+ for (const child of item.children) {
282
+ nodes.push(
283
+ <NavLink key={`child:${item.path}:${child.path}`} item={child} ctx={ctx} nested />,
284
+ )
285
+ }
286
+ }
287
+ }
288
+
289
+ return nodes
290
+ }
291
+
292
+ function NavLink({
293
+ item,
294
+ ctx,
295
+ nested = false,
296
+ }: {
297
+ item: NavItem
298
+ ctx: NavRenderContext
299
+ nested?: boolean
300
+ }) {
301
+ const Icon = item.icon
302
+ const isActive = isPathActive(ctx.currentPath, item.path)
303
+
304
+ return (
305
+ <button
306
+ onClick={() => ctx.onNavigate(item.path)}
307
+ className={`flex w-full items-center gap-3 rounded-lg text-left transition-colors ${
308
+ nested ? 'py-1.5 pr-3 pl-9' : 'px-3 py-2.5'
309
+ } ${
310
+ isActive
311
+ ? 'bg-sidebar-accent text-sidebar-primary'
312
+ : 'text-sidebar-foreground hover:bg-sidebar-accent'
313
+ } ${ctx.collapsed ? 'justify-center px-3' : ''}`}
314
+ title={ctx.collapsed ? item.label : ''}
315
+ aria-current={isActive ? 'page' : undefined}
316
+ >
317
+ {!nested && <Icon className="h-5 w-5 shrink-0" aria-hidden />}
318
+ {nested && !ctx.collapsed && (
319
+ <Icon className="text-sidebar-foreground/60 h-4 w-4 shrink-0" aria-hidden />
320
+ )}
321
+ {!ctx.collapsed && (
322
+ <span className={`truncate font-medium ${nested ? 'text-[13px]' : 'text-sm'}`}>
323
+ {item.label}
324
+ </span>
325
+ )}
326
+ </button>
327
+ )
328
+ }
329
+
330
+ // ─── Config-driven nav builder ──────────────────────────────────────────────
331
+
212
332
  function resolveIcon(collection: any): LucideIcon {
213
333
  const mapped = collection.admin?.icon ? ICON_MAP[collection.admin.icon] : undefined
214
334
  if (mapped) return mapped
@@ -219,28 +339,50 @@ export interface NavItem {
219
339
  path: string
220
340
  label: string
221
341
  icon: LucideIcon
342
+ /**
343
+ * Section header. Items sharing the same `group` are grouped together
344
+ * under a header rendered on first encounter. Items without a `group`
345
+ * render at the top of the nav in document order.
346
+ */
222
347
  group?: string
348
+ /** Indented children rendered immediately after this item. */
349
+ children?: NavItem[]
223
350
  }
224
351
 
225
- function buildNavItems(config: any): NavItem[] {
352
+ /**
353
+ * Build the sidebar nav tree from the CMS config. If no collections are
354
+ * configured, returns the curated `defaultNavItems` tree. When
355
+ * collections ARE configured, page-type collections inherit the
356
+ * Pages / Saved Sections / Templates child block, and every static
357
+ * destination (Media, Forms, SEO, Script Tags, Users, API Keys,
358
+ * Settings) is anchored under the right Content / Settings group.
359
+ */
360
+ export function buildNavItems(config: any): NavItem[] {
226
361
  if (!config?.collections) return defaultNavItems
227
362
 
228
363
  const raw = config.collections
229
364
  const collectionsList: any[] = Array.isArray(raw) ? raw : Object.values(raw)
230
-
231
365
  const visible = collectionsList.filter((c) => !c.admin?.hidden)
232
366
 
233
- const ungrouped = visible.filter((c) => !c.admin?.group)
234
- const grouped = visible.filter((c) => c.admin?.group)
235
-
236
- const pages = ungrouped.filter((c) => c.type === 'page')
237
- const posts = ungrouped.filter((c) => c.type === 'post')
238
- const other = ungrouped.filter((c) => c.type !== 'page' && c.type !== 'post')
239
- const sortedUngrouped = [...pages, ...posts, ...other]
367
+ // Treat `admin.group: 'Posts'` (case-insensitive) as a synonym for
368
+ // `type: 'post'` so consumers can tag arbitrary collections into
369
+ // the Posts area without changing the discriminator they already
370
+ // use elsewhere (sitemaps, schema.org, etc.).
371
+ const isPostsGroup = (c: any) =>
372
+ typeof c.admin?.group === 'string' && c.admin.group.toLowerCase() === 'posts'
373
+ const pages = visible.filter((c) => c.type === 'page' && !c.admin?.group)
374
+ const posts = visible.filter((c) => c.type === 'post' || isPostsGroup(c))
375
+ const otherUngrouped = visible.filter(
376
+ (c) => c.type !== 'page' && c.type !== 'post' && !c.admin?.group,
377
+ )
378
+ // Anything explicitly grouped that isn't a posts collection.
379
+ const grouped = visible.filter((c) => c.admin?.group && !isPostsGroup(c) && c.type !== 'post')
240
380
 
241
381
  const items: NavItem[] = [{ path: '/', label: 'Dashboard', icon: LayoutDashboard }]
242
382
 
243
- for (const collection of sortedUngrouped) {
383
+ // Ungrouped collections that are neither page- nor post-type stay at
384
+ // the top level, directly under Dashboard, before the CONTENT group.
385
+ for (const collection of otherUngrouped) {
244
386
  items.push({
245
387
  label: collection.labels?.plural ?? collection.slug,
246
388
  path: `/${collection.slug}`,
@@ -248,34 +390,78 @@ function buildNavItems(config: any): NavItem[] {
248
390
  })
249
391
  }
250
392
 
251
- const groups = new Map<string, typeof grouped>()
393
+ // CONTENT group. Everything below shares `group: 'Content'`, so the
394
+ // renderer emits the "CONTENT" header once — above Pages. Order:
395
+ // Pages → Posts → static content destinations (Media / Forms / SEO /
396
+ // Script Tags).
397
+
398
+ // Pages-style collections render as plain Content entries (no nested
399
+ // children). Saved Sections / Widgets now live under the dedicated
400
+ // Templates parent below, not under Pages.
401
+ pages.forEach((collection) => {
402
+ items.push({
403
+ label: collection.labels?.plural ?? collection.slug,
404
+ path: `/${collection.slug}`,
405
+ icon: resolveIcon(collection),
406
+ group: 'Content',
407
+ })
408
+ })
409
+
410
+ // Posts area — collapses every post-type collection under a single
411
+ // "Posts" parent. The aggregated table at /posts and the type cards
412
+ // at /posts/types are the entry points; individual post-type
413
+ // collections are not enumerated in the sidebar to avoid the
414
+ // duplicate-nav problem when a project has many types.
415
+ if (posts.length > 0) {
416
+ items.push({
417
+ path: '/posts',
418
+ label: 'Posts',
419
+ icon: FileText,
420
+ group: 'Content',
421
+ children: POSTS_CHILDREN,
422
+ })
423
+ }
424
+
425
+ // Templates — its own parent holding Saved Sections + Widgets (page
426
+ // building structure, distinct from Pages content).
427
+ items.push({
428
+ path: '/page-templates',
429
+ label: 'Templates',
430
+ icon: Layers,
431
+ group: 'Content',
432
+ children: TEMPLATES_CHILDREN,
433
+ })
434
+
435
+ // Standard Content destinations (always present — they map to CMS
436
+ // features, not collections).
437
+ items.push(
438
+ { path: '/media', label: 'Media', icon: Image, group: 'Content' },
439
+ { path: '/forms', label: 'Forms', icon: ClipboardList, group: 'Content' },
440
+ { path: '/seo', label: 'SEO', icon: SearchIcon, group: 'Content' },
441
+ { path: '/script-tags', label: 'Script Tags', icon: Code2, group: 'Content' },
442
+ )
443
+
444
+ // Config-supplied custom groups land after the standard Content block
445
+ // and before Settings. Each renders under its own header.
446
+ const groupBuckets = new Map<string, NavItem[]>()
252
447
  for (const col of grouped) {
253
448
  const group = col.admin.group as string
254
- if (!groups.has(group)) groups.set(group, [])
255
- groups.get(group)!.push(col)
256
- }
257
- for (const [groupName, cols] of groups) {
258
- for (const collection of cols) {
259
- items.push({
260
- label: collection.labels?.plural ?? collection.slug,
261
- path: `/${collection.slug}`,
262
- icon: resolveIcon(collection),
263
- group: groupName,
264
- })
265
- }
449
+ const bucket = groupBuckets.get(group) ?? []
450
+ bucket.push({
451
+ label: col.labels?.plural ?? col.slug,
452
+ path: `/${col.slug}`,
453
+ icon: resolveIcon(col),
454
+ group,
455
+ })
456
+ groupBuckets.set(group, bucket)
266
457
  }
458
+ for (const bucket of groupBuckets.values()) items.push(...bucket)
267
459
 
460
+ // Settings destinations — always last.
268
461
  items.push(
269
- { path: '/page-builder/new', label: 'Page Builder', icon: LayoutTemplate, group: 'Pages' },
270
- { path: '/saved-sections', label: 'Saved Sections', icon: Library, group: 'Pages' },
271
- { path: '/page-templates', label: 'Templates', icon: Layers, group: 'Pages' },
272
- { path: '/media', label: 'Media', icon: Image },
273
- { path: '/forms', label: 'Forms', icon: ClipboardList },
274
- { path: '/seo', label: 'SEO', icon: SearchIcon },
275
- { path: '/script-tags', label: 'Script Tags', icon: Code2 },
276
- { path: '/users', label: 'Users', icon: Users },
277
- { path: '/api-keys', label: 'API Keys', icon: KeyRound },
278
- { path: '/settings', label: 'Settings', icon: Settings },
462
+ { path: '/users', label: 'Users', icon: Users, group: 'Settings' },
463
+ { path: '/api-keys', label: 'API Keys', icon: KeyRound, group: 'Settings' },
464
+ { path: '/settings', label: 'Settings', icon: Settings, group: 'Settings' },
279
465
  )
280
466
 
281
467
  return items