@actuate-media/cms-admin 0.10.0 → 0.12.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 (315) hide show
  1. package/LICENSE +21 -21
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +8 -5
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/fields/component-block-helpers.test.d.ts +7 -0
  6. package/dist/__tests__/fields/component-block-helpers.test.d.ts.map +1 -0
  7. package/dist/__tests__/fields/component-block-helpers.test.js +592 -0
  8. package/dist/__tests__/fields/component-block-helpers.test.js.map +1 -0
  9. package/dist/__tests__/layout/primitives.test.d.ts +2 -0
  10. package/dist/__tests__/layout/primitives.test.d.ts.map +1 -0
  11. package/dist/__tests__/layout/primitives.test.js +34 -0
  12. package/dist/__tests__/layout/primitives.test.js.map +1 -0
  13. package/dist/__tests__/lib/cv.test.d.ts +2 -0
  14. package/dist/__tests__/lib/cv.test.d.ts.map +1 -0
  15. package/dist/__tests__/lib/cv.test.js +66 -0
  16. package/dist/__tests__/lib/cv.test.js.map +1 -0
  17. package/dist/actuate-admin.css +1 -1
  18. package/dist/assets/actuate-logo.d.ts +36 -0
  19. package/dist/assets/actuate-logo.d.ts.map +1 -0
  20. package/dist/assets/actuate-logo.js +15 -0
  21. package/dist/assets/actuate-logo.js.map +1 -0
  22. package/dist/components/Breadcrumbs.js +2 -2
  23. package/dist/components/CommandPalette.js +10 -10
  24. package/dist/components/ContentOverviewChart.js +3 -3
  25. package/dist/components/ErrorBoundary.js +1 -1
  26. package/dist/components/FocalPointPicker.js +2 -2
  27. package/dist/components/FolderTree.js +20 -20
  28. package/dist/components/LivePreview.js +3 -3
  29. package/dist/components/LocaleSwitcher.js +1 -1
  30. package/dist/components/MediaPickerModal.js +4 -4
  31. package/dist/components/PresenceIndicator.js +1 -1
  32. package/dist/components/SEOConfigPanel.d.ts +2 -0
  33. package/dist/components/SEOConfigPanel.d.ts.map +1 -0
  34. package/dist/components/SEOConfigPanel.js +174 -0
  35. package/dist/components/SEOConfigPanel.js.map +1 -0
  36. package/dist/components/SEOPanel.js +9 -9
  37. package/dist/components/SEOPerformance.js +2 -2
  38. package/dist/components/SchedulePublishDialog.js +1 -1
  39. package/dist/components/SharePreviewLinkDialog.js +1 -1
  40. package/dist/components/TipTapEditor.js +5 -5
  41. package/dist/components/VersionHistory.js +2 -2
  42. package/dist/components/ui/Badge.d.ts +33 -3
  43. package/dist/components/ui/Badge.d.ts.map +1 -1
  44. package/dist/components/ui/Badge.js +42 -8
  45. package/dist/components/ui/Badge.js.map +1 -1
  46. package/dist/components/ui/Button.d.ts +19 -8
  47. package/dist/components/ui/Button.d.ts.map +1 -1
  48. package/dist/components/ui/Button.js +35 -14
  49. package/dist/components/ui/Button.js.map +1 -1
  50. package/dist/components/ui/Card.d.ts +26 -0
  51. package/dist/components/ui/Card.d.ts.map +1 -0
  52. package/dist/components/ui/Card.js +45 -0
  53. package/dist/components/ui/Card.js.map +1 -0
  54. package/dist/components/ui/DataTable.js +1 -1
  55. package/dist/components/ui/Input.d.ts +15 -0
  56. package/dist/components/ui/Input.d.ts.map +1 -0
  57. package/dist/components/ui/Input.js +23 -0
  58. package/dist/components/ui/Input.js.map +1 -0
  59. package/dist/components/ui/SearchInput.js +1 -1
  60. package/dist/components/ui/Select.d.ts +16 -0
  61. package/dist/components/ui/Select.d.ts.map +1 -0
  62. package/dist/components/ui/Select.js +25 -0
  63. package/dist/components/ui/Select.js.map +1 -0
  64. package/dist/components/ui/Toast.js +1 -1
  65. package/dist/components/ui/index.d.ts +10 -4
  66. package/dist/components/ui/index.d.ts.map +1 -1
  67. package/dist/components/ui/index.js +5 -2
  68. package/dist/components/ui/index.js.map +1 -1
  69. package/dist/fields/BlockBuilderField.js +3 -3
  70. package/dist/fields/ComponentBlockField.d.ts +25 -0
  71. package/dist/fields/ComponentBlockField.d.ts.map +1 -0
  72. package/dist/fields/ComponentBlockField.js +74 -0
  73. package/dist/fields/ComponentBlockField.js.map +1 -0
  74. package/dist/fields/DateField.js +1 -1
  75. package/dist/fields/FieldRenderer.d.ts +3 -0
  76. package/dist/fields/FieldRenderer.d.ts.map +1 -1
  77. package/dist/fields/FieldRenderer.js +3 -1
  78. package/dist/fields/FieldRenderer.js.map +1 -1
  79. package/dist/fields/PropInput.d.ts +14 -0
  80. package/dist/fields/PropInput.d.ts.map +1 -0
  81. package/dist/fields/PropInput.js +163 -0
  82. package/dist/fields/PropInput.js.map +1 -0
  83. package/dist/fields/RelationshipField.js +3 -3
  84. package/dist/fields/TextField.js +1 -1
  85. package/dist/fields/component-block-helpers.d.ts +96 -0
  86. package/dist/fields/component-block-helpers.d.ts.map +1 -0
  87. package/dist/fields/component-block-helpers.js +323 -0
  88. package/dist/fields/component-block-helpers.js.map +1 -0
  89. package/dist/fields/index.d.ts +4 -0
  90. package/dist/fields/index.d.ts.map +1 -1
  91. package/dist/fields/index.js +2 -0
  92. package/dist/fields/index.js.map +1 -1
  93. package/dist/index.d.ts +4 -0
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +4 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/layout/Header.js +1 -1
  98. package/dist/layout/Layout.d.ts +14 -0
  99. package/dist/layout/Layout.d.ts.map +1 -1
  100. package/dist/layout/Layout.js +17 -11
  101. package/dist/layout/Layout.js.map +1 -1
  102. package/dist/layout/Sidebar.d.ts.map +1 -1
  103. package/dist/layout/Sidebar.js +21 -11
  104. package/dist/layout/Sidebar.js.map +1 -1
  105. package/dist/layout/primitives/AdminShell.d.ts +43 -0
  106. package/dist/layout/primitives/AdminShell.d.ts.map +1 -0
  107. package/dist/layout/primitives/AdminShell.js +51 -0
  108. package/dist/layout/primitives/AdminShell.js.map +1 -0
  109. package/dist/layout/primitives/Box.d.ts +19 -0
  110. package/dist/layout/primitives/Box.d.ts.map +1 -0
  111. package/dist/layout/primitives/Box.js +12 -0
  112. package/dist/layout/primitives/Box.js.map +1 -0
  113. package/dist/layout/primitives/Cluster.d.ts +27 -0
  114. package/dist/layout/primitives/Cluster.d.ts.map +1 -0
  115. package/dist/layout/primitives/Cluster.js +37 -0
  116. package/dist/layout/primitives/Cluster.js.map +1 -0
  117. package/dist/layout/primitives/Grid.d.ts +45 -0
  118. package/dist/layout/primitives/Grid.d.ts.map +1 -0
  119. package/dist/layout/primitives/Grid.js +59 -0
  120. package/dist/layout/primitives/Grid.js.map +1 -0
  121. package/dist/layout/primitives/PageContainer.d.ts +36 -0
  122. package/dist/layout/primitives/PageContainer.d.ts.map +1 -0
  123. package/dist/layout/primitives/PageContainer.js +41 -0
  124. package/dist/layout/primitives/PageContainer.js.map +1 -0
  125. package/dist/layout/primitives/Split.d.ts +34 -0
  126. package/dist/layout/primitives/Split.d.ts.map +1 -0
  127. package/dist/layout/primitives/Split.js +27 -0
  128. package/dist/layout/primitives/Split.js.map +1 -0
  129. package/dist/layout/primitives/Stack.d.ts +23 -0
  130. package/dist/layout/primitives/Stack.d.ts.map +1 -0
  131. package/dist/layout/primitives/Stack.js +34 -0
  132. package/dist/layout/primitives/Stack.js.map +1 -0
  133. package/dist/layout/primitives/index.d.ts +30 -0
  134. package/dist/layout/primitives/index.d.ts.map +1 -0
  135. package/dist/layout/primitives/index.js +22 -0
  136. package/dist/layout/primitives/index.js.map +1 -0
  137. package/dist/layout/primitives/tokens.d.ts +48 -0
  138. package/dist/layout/primitives/tokens.d.ts.map +1 -0
  139. package/dist/layout/primitives/tokens.js +54 -0
  140. package/dist/layout/primitives/tokens.js.map +1 -0
  141. package/dist/lib/cv.d.ts +53 -0
  142. package/dist/lib/cv.d.ts.map +1 -0
  143. package/dist/lib/cv.js +39 -0
  144. package/dist/lib/cv.js.map +1 -0
  145. package/dist/views/ApiKeys.js +7 -7
  146. package/dist/views/CollectionList.js +8 -8
  147. package/dist/views/Dashboard.d.ts.map +1 -1
  148. package/dist/views/Dashboard.js +333 -78
  149. package/dist/views/Dashboard.js.map +1 -1
  150. package/dist/views/DocumentEdit.js +3 -3
  151. package/dist/views/ForgotPassword.js +2 -2
  152. package/dist/views/FormEditor.js +5 -5
  153. package/dist/views/FormSubmissions.js +6 -6
  154. package/dist/views/Forms.js +2 -2
  155. package/dist/views/Login.d.ts +16 -1
  156. package/dist/views/Login.d.ts.map +1 -1
  157. package/dist/views/Login.js +17 -7
  158. package/dist/views/Login.js.map +1 -1
  159. package/dist/views/MediaBrowser.js +16 -16
  160. package/dist/views/PageEditor.js +2 -2
  161. package/dist/views/Pages.js +10 -10
  162. package/dist/views/PostEditor.js +2 -2
  163. package/dist/views/Posts.js +4 -4
  164. package/dist/views/Redirects.js +4 -4
  165. package/dist/views/ResetPassword.js +2 -2
  166. package/dist/views/SEO.js +6 -6
  167. package/dist/views/ScriptTagEditor.js +4 -4
  168. package/dist/views/ScriptTags.js +2 -2
  169. package/dist/views/Settings.d.ts.map +1 -1
  170. package/dist/views/Settings.js +9 -8
  171. package/dist/views/Settings.js.map +1 -1
  172. package/dist/views/SetupWizard.js +2 -2
  173. package/dist/views/Users.js +4 -4
  174. package/dist/views/page-builder/AIBlockAssist.js +1 -1
  175. package/dist/views/page-builder/AIGenerateDialog.js +10 -10
  176. package/dist/views/page-builder/BlockEditor.js +10 -10
  177. package/dist/views/page-builder/BlockPicker.js +4 -4
  178. package/dist/views/page-builder/BottomBar.js +1 -1
  179. package/dist/views/page-builder/BuilderToolbar.js +2 -2
  180. package/dist/views/page-builder/ContextPanel.js +2 -2
  181. package/dist/views/page-builder/DesignScore.js +9 -9
  182. package/dist/views/page-builder/NodeSettings.js +8 -8
  183. package/dist/views/page-builder/PageBuilder.js +3 -3
  184. package/dist/views/page-builder/PageSettings.js +1 -1
  185. package/dist/views/page-builder/PageTemplates.js +2 -2
  186. package/dist/views/page-builder/SEOPanel.js +13 -13
  187. package/dist/views/page-builder/SavedSections.js +5 -5
  188. package/dist/views/page-builder/TemplatePicker.js +2 -2
  189. package/dist/views/page-builder/block-renderers/CTAPreview.js +5 -5
  190. package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
  191. package/dist/views/page-builder/block-renderers/CodePreview.js +1 -1
  192. package/dist/views/page-builder/block-renderers/FAQPreview.js +3 -3
  193. package/dist/views/page-builder/block-renderers/FallbackPreview.js +1 -1
  194. package/dist/views/page-builder/block-renderers/FormPreview.js +3 -3
  195. package/dist/views/page-builder/block-renderers/GalleryPreview.js +5 -5
  196. package/dist/views/page-builder/block-renderers/HeroPreview.js +3 -3
  197. package/dist/views/page-builder/block-renderers/ImagePreview.js +3 -3
  198. package/dist/views/page-builder/block-renderers/TextPreview.js +3 -3
  199. package/dist/views/page-builder/block-renderers/VideoPreview.js +4 -4
  200. package/dist/views/page-builder/canvas/BlockRenderer.js +1 -1
  201. package/dist/views/page-builder/canvas/BuilderCanvas.js +3 -3
  202. package/dist/views/page-builder/canvas/ColumnRenderer.js +2 -2
  203. package/dist/views/page-builder/canvas/ContainerRenderer.js +2 -2
  204. package/dist/views/page-builder/canvas/RowRenderer.js +2 -2
  205. package/dist/views/page-builder/canvas/SectionRenderer.js +2 -2
  206. package/package.json +14 -3
  207. package/src/AdminRoot.tsx +21 -11
  208. package/src/__tests__/fields/component-block-helpers.test.ts +674 -0
  209. package/src/__tests__/layout/primitives.test.ts +37 -0
  210. package/src/__tests__/lib/cv.test.ts +74 -0
  211. package/src/assets/actuate-logo.tsx +72 -0
  212. package/src/components/Breadcrumbs.tsx +6 -6
  213. package/src/components/CommandPalette.tsx +34 -34
  214. package/src/components/ContentOverviewChart.tsx +3 -3
  215. package/src/components/ErrorBoundary.tsx +3 -3
  216. package/src/components/FocalPointPicker.tsx +4 -4
  217. package/src/components/FolderTree.tsx +38 -38
  218. package/src/components/LivePreview.tsx +16 -16
  219. package/src/components/LocaleSwitcher.tsx +7 -7
  220. package/src/components/MediaPickerModal.tsx +21 -21
  221. package/src/components/PresenceIndicator.tsx +2 -2
  222. package/src/components/SEOConfigPanel.tsx +582 -0
  223. package/src/components/SEOPanel.tsx +46 -46
  224. package/src/components/SEOPerformance.tsx +21 -21
  225. package/src/components/SchedulePublishDialog.tsx +4 -4
  226. package/src/components/SharePreviewLinkDialog.tsx +1 -1
  227. package/src/components/TipTapEditor.tsx +33 -33
  228. package/src/components/VersionHistory.tsx +16 -16
  229. package/src/components/ui/Badge.tsx +66 -14
  230. package/src/components/ui/Button.tsx +70 -33
  231. package/src/components/ui/Card.tsx +101 -0
  232. package/src/components/ui/DataTable.tsx +1 -1
  233. package/src/components/ui/Input.tsx +35 -0
  234. package/src/components/ui/SearchInput.tsx +4 -4
  235. package/src/components/ui/Select.tsx +56 -0
  236. package/src/components/ui/Toast.tsx +1 -1
  237. package/src/components/ui/index.ts +18 -4
  238. package/src/fields/BlockBuilderField.tsx +3 -3
  239. package/src/fields/ComponentBlockField.tsx +179 -0
  240. package/src/fields/DateField.tsx +1 -1
  241. package/src/fields/FieldRenderer.tsx +8 -0
  242. package/src/fields/PropInput.tsx +552 -0
  243. package/src/fields/RelationshipField.tsx +10 -10
  244. package/src/fields/TextField.tsx +1 -1
  245. package/src/fields/component-block-helpers.ts +341 -0
  246. package/src/fields/index.ts +4 -0
  247. package/src/index.ts +35 -0
  248. package/src/layout/Header.tsx +28 -28
  249. package/src/layout/Layout.tsx +39 -46
  250. package/src/layout/Sidebar.tsx +37 -64
  251. package/src/layout/primitives/AdminShell.tsx +118 -0
  252. package/src/layout/primitives/Box.tsx +30 -0
  253. package/src/layout/primitives/Cluster.tsx +74 -0
  254. package/src/layout/primitives/Grid.tsx +120 -0
  255. package/src/layout/primitives/PageContainer.tsx +96 -0
  256. package/src/layout/primitives/Split.tsx +73 -0
  257. package/src/layout/primitives/Stack.tsx +67 -0
  258. package/src/layout/primitives/index.ts +36 -0
  259. package/src/layout/primitives/tokens.ts +76 -0
  260. package/src/lib/cv.ts +96 -0
  261. package/src/styles/build-input.css +1 -1
  262. package/src/views/ApiKeys.tsx +57 -57
  263. package/src/views/CollectionList.tsx +30 -30
  264. package/src/views/Dashboard.tsx +737 -186
  265. package/src/views/DocumentEdit.tsx +9 -9
  266. package/src/views/ForgotPassword.tsx +18 -18
  267. package/src/views/FormEditor.tsx +75 -75
  268. package/src/views/FormSubmissions.tsx +76 -76
  269. package/src/views/Forms.tsx +27 -27
  270. package/src/views/Login.tsx +65 -25
  271. package/src/views/MediaBrowser.tsx +127 -127
  272. package/src/views/PageEditor.tsx +25 -25
  273. package/src/views/Pages.tsx +59 -59
  274. package/src/views/PostEditor.tsx +37 -37
  275. package/src/views/Posts.tsx +48 -48
  276. package/src/views/Redirects.tsx +21 -21
  277. package/src/views/ResetPassword.tsx +28 -28
  278. package/src/views/SEO.tsx +144 -144
  279. package/src/views/ScriptTagEditor.tsx +24 -24
  280. package/src/views/ScriptTags.tsx +10 -10
  281. package/src/views/Settings.tsx +88 -80
  282. package/src/views/SetupWizard.tsx +28 -28
  283. package/src/views/Users.tsx +20 -20
  284. package/src/views/page-builder/AIBlockAssist.tsx +1 -1
  285. package/src/views/page-builder/AIGenerateDialog.tsx +63 -63
  286. package/src/views/page-builder/BlockEditor.tsx +26 -26
  287. package/src/views/page-builder/BlockPicker.tsx +22 -22
  288. package/src/views/page-builder/BottomBar.tsx +8 -8
  289. package/src/views/page-builder/BuilderToolbar.tsx +17 -17
  290. package/src/views/page-builder/ContextPanel.tsx +3 -3
  291. package/src/views/page-builder/DesignScore.tsx +21 -21
  292. package/src/views/page-builder/NodeSettings.tsx +27 -27
  293. package/src/views/page-builder/PageBuilder.tsx +11 -11
  294. package/src/views/page-builder/PageSettings.tsx +4 -4
  295. package/src/views/page-builder/PageTemplates.tsx +18 -18
  296. package/src/views/page-builder/SEOPanel.tsx +53 -53
  297. package/src/views/page-builder/SavedSections.tsx +37 -37
  298. package/src/views/page-builder/TemplatePicker.tsx +17 -17
  299. package/src/views/page-builder/block-renderers/CTAPreview.tsx +13 -13
  300. package/src/views/page-builder/block-renderers/CardsPreview.tsx +5 -5
  301. package/src/views/page-builder/block-renderers/CodePreview.tsx +6 -6
  302. package/src/views/page-builder/block-renderers/FAQPreview.tsx +13 -13
  303. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +3 -3
  304. package/src/views/page-builder/block-renderers/FormPreview.tsx +20 -20
  305. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +8 -8
  306. package/src/views/page-builder/block-renderers/HeroPreview.tsx +16 -16
  307. package/src/views/page-builder/block-renderers/ImagePreview.tsx +4 -4
  308. package/src/views/page-builder/block-renderers/TextPreview.tsx +14 -14
  309. package/src/views/page-builder/block-renderers/VideoPreview.tsx +12 -12
  310. package/src/views/page-builder/canvas/BlockRenderer.tsx +4 -4
  311. package/src/views/page-builder/canvas/BuilderCanvas.tsx +6 -6
  312. package/src/views/page-builder/canvas/ColumnRenderer.tsx +3 -3
  313. package/src/views/page-builder/canvas/ContainerRenderer.tsx +2 -2
  314. package/src/views/page-builder/canvas/RowRenderer.tsx +2 -2
  315. package/src/views/page-builder/canvas/SectionRenderer.tsx +2 -2
@@ -0,0 +1,35 @@
1
+ import { forwardRef, type InputHTMLAttributes } from 'react'
2
+ import { cv, type VariantProps } from '../../lib/cv.js'
3
+
4
+ const input = cv(
5
+ 'focus-visible:ring-offset-background flex w-full rounded-[var(--radius)] border bg-[var(--background)] text-[var(--foreground)] file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[var(--muted-foreground)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
6
+ {
7
+ variants: {
8
+ size: {
9
+ sm: 'h-8 px-2.5 text-xs',
10
+ md: 'h-9 px-3 text-sm',
11
+ lg: 'h-10 px-4 text-base',
12
+ },
13
+ state: {
14
+ default: 'border-[var(--border)]',
15
+ invalid: 'border-[var(--destructive)] focus-visible:ring-[var(--destructive)]',
16
+ valid: 'border-emerald-500 focus-visible:ring-emerald-500',
17
+ },
18
+ },
19
+ defaultVariants: { size: 'md', state: 'default' },
20
+ },
21
+ )
22
+
23
+ export type InputVariants = VariantProps<typeof input>
24
+
25
+ export interface InputProps
26
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>, InputVariants {}
27
+
28
+ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
29
+ { size, state, className, ...rest },
30
+ ref,
31
+ ) {
32
+ return <input ref={ref} className={input({ size, state, class: className })} {...rest} />
33
+ })
34
+
35
+ export { input as inputVariants }
@@ -10,7 +10,7 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
10
10
  return (
11
11
  <div className="relative flex-1">
12
12
  <svg
13
- className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--muted-foreground)]"
13
+ className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--muted-foreground)]"
14
14
  fill="none"
15
15
  viewBox="0 0 24 24"
16
16
  stroke="currentColor"
@@ -27,12 +27,12 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
27
27
  value={value}
28
28
  onChange={(e) => onChange(e.target.value)}
29
29
  placeholder={placeholder}
30
- className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] py-2 pl-9 pr-8 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
30
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] py-2 pr-8 pl-9 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
31
31
  />
32
32
  {value && (
33
33
  <button
34
34
  onClick={() => onChange('')}
35
- className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
35
+ className="absolute top-1/2 right-2 -translate-y-1/2 rounded p-0.5 text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
36
36
  aria-label="Clear search"
37
37
  >
38
38
  <svg
@@ -46,7 +46,7 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
46
46
  </svg>
47
47
  </button>
48
48
  )}
49
- <kbd className="pointer-events-none absolute right-8 top-1/2 hidden -translate-y-1/2 rounded border border-[var(--border)] px-1.5 py-0.5 text-[10px] text-[var(--muted-foreground)] sm:inline-block">
49
+ <kbd className="pointer-events-none absolute top-1/2 right-8 hidden -translate-y-1/2 rounded border border-[var(--border)] px-1.5 py-0.5 text-[10px] text-[var(--muted-foreground)] sm:inline-block">
50
50
  ⌘K
51
51
  </kbd>
52
52
  </div>
@@ -0,0 +1,56 @@
1
+ import { forwardRef, type SelectHTMLAttributes, type ReactNode } from 'react'
2
+ import { cv, type VariantProps } from '../../lib/cv.js'
3
+
4
+ const select = cv(
5
+ // Shares geometry with Input so they line up in forms.
6
+ 'focus-visible:ring-offset-background flex w-full appearance-none rounded-[var(--radius)] border bg-[var(--background)] pr-8 text-[var(--foreground)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
7
+ {
8
+ variants: {
9
+ size: {
10
+ sm: 'h-8 pr-7 pl-2.5 text-xs',
11
+ md: 'h-9 pr-8 pl-3 text-sm',
12
+ lg: 'h-10 pr-9 pl-4 text-base',
13
+ },
14
+ state: {
15
+ default: 'border-[var(--border)]',
16
+ invalid: 'border-[var(--destructive)] focus-visible:ring-[var(--destructive)]',
17
+ valid: 'border-emerald-500 focus-visible:ring-emerald-500',
18
+ },
19
+ },
20
+ defaultVariants: { size: 'md', state: 'default' },
21
+ },
22
+ )
23
+
24
+ export type SelectVariants = VariantProps<typeof select>
25
+
26
+ export interface SelectProps
27
+ extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'>, SelectVariants {
28
+ children?: ReactNode
29
+ }
30
+
31
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(
32
+ { size, state, className, children, ...rest },
33
+ ref,
34
+ ) {
35
+ return (
36
+ <div className="relative">
37
+ <select ref={ref} className={select({ size, state, class: className })} {...rest}>
38
+ {children}
39
+ </select>
40
+ <svg
41
+ aria-hidden
42
+ className="pointer-events-none absolute top-1/2 right-2 h-4 w-4 -translate-y-1/2 text-[var(--muted-foreground)]"
43
+ viewBox="0 0 20 20"
44
+ fill="currentColor"
45
+ >
46
+ <path
47
+ fillRule="evenodd"
48
+ d="M5.23 7.21a.75.75 0 011.06.02L10 11.06l3.71-3.83a.75.75 0 111.08 1.04l-4.25 4.39a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z"
49
+ clipRule="evenodd"
50
+ />
51
+ </svg>
52
+ </div>
53
+ )
54
+ })
55
+
56
+ export { select as selectVariants }
@@ -45,7 +45,7 @@ export function ToastContainer({ toasts, onDismiss }: ToastContainerProps) {
45
45
  if (toasts.length === 0) return null
46
46
 
47
47
  return (
48
- <div className="fixed bottom-4 right-4 z-[100] flex flex-col gap-2">
48
+ <div className="fixed right-4 bottom-4 z-[100] flex flex-col gap-2">
49
49
  {toasts.map((toast) => (
50
50
  <div
51
51
  key={toast.id}
@@ -1,7 +1,21 @@
1
- export { Button } from './Button.js'
2
- export type { ButtonProps } from './Button.js'
3
- export { Badge } from './Badge.js'
4
- export type { BadgeProps } from './Badge.js'
1
+ export { Button, buttonVariants } from './Button.js'
2
+ export type { ButtonProps, ButtonVariants } from './Button.js'
3
+ export { Badge, badgeVariants, STATUS_TONE, STATUS_LABEL } from './Badge.js'
4
+ export type { BadgeProps, BadgeVariants, DocumentStatus } from './Badge.js'
5
+ export {
6
+ Card,
7
+ CardHeader,
8
+ CardTitle,
9
+ CardDescription,
10
+ CardContent,
11
+ CardFooter,
12
+ cardVariants,
13
+ } from './Card.js'
14
+ export type { CardProps, CardVariants } from './Card.js'
15
+ export { Input, inputVariants } from './Input.js'
16
+ export type { InputProps, InputVariants } from './Input.js'
17
+ export { Select, selectVariants } from './Select.js'
18
+ export type { SelectProps, SelectVariants } from './Select.js'
5
19
  export { Avatar } from './Avatar.js'
6
20
  export type { AvatarProps } from './Avatar.js'
7
21
  export { EmptyState } from './EmptyState.js'
@@ -74,7 +74,7 @@ function BlockField({ field, value, onChange }: BlockFieldProps) {
74
74
  <select
75
75
  value={(value as string) ?? ''}
76
76
  onChange={(e) => onChange(e.target.value)}
77
- className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm outline-none transition-colors focus:ring-2 focus:ring-[var(--ring)]"
77
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm transition-colors outline-none focus:ring-2 focus:ring-[var(--ring)]"
78
78
  >
79
79
  <option value="">Select...</option>
80
80
  {field.options.map((opt) => (
@@ -98,7 +98,7 @@ function BlockField({ field, value, onChange }: BlockFieldProps) {
98
98
  value={(value as string) ?? ''}
99
99
  onChange={(e) => onChange(e.target.value)}
100
100
  rows={4}
101
- className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm outline-none transition-colors focus:ring-2 focus:ring-[var(--ring)]"
101
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm transition-colors outline-none focus:ring-2 focus:ring-[var(--ring)]"
102
102
  />
103
103
  </div>
104
104
  )
@@ -134,7 +134,7 @@ function BlockField({ field, value, onChange }: BlockFieldProps) {
134
134
  value={(value as string) ?? ''}
135
135
  onChange={(e) => onChange(e.target.value)}
136
136
  required={field.required}
137
- className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm outline-none transition-colors focus:ring-2 focus:ring-[var(--ring)]"
137
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm transition-colors outline-none focus:ring-2 focus:ring-[var(--ring)]"
138
138
  />
139
139
  </div>
140
140
  )
@@ -0,0 +1,179 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Top-level admin field for component-aware blocks. Consumes a
5
+ * {@link Manifest} produced by `@actuate-media/component-blocks`,
6
+ * renders a component picker (when more than one component is allowed)
7
+ * and a recursive props form via {@link PropInput}.
8
+ *
9
+ * Storage shape:
10
+ * { component: 'Hero', props: { title: 'Welcome', alignment: 'center' } }
11
+ *
12
+ * Live validation runs through `validateComponentBlockValue` from
13
+ * `@actuate-media/cms-core`. The whole-value error is shown at the top
14
+ * of the form, and the per-prop error map (parsed out of the message
15
+ * string) is forwarded into `PropInput` so each input can render its
16
+ * own inline error.
17
+ */
18
+
19
+ import { useEffect, useMemo, useRef, useState } from 'react'
20
+
21
+ import type { Manifest } from '@actuate-media/component-blocks'
22
+
23
+ import {
24
+ buildClientValidator,
25
+ getAllowedComponents,
26
+ parsePerPropErrors,
27
+ seedPropsForComponent,
28
+ } from './component-block-helpers.js'
29
+ import { PropInput } from './PropInput.js'
30
+
31
+ export interface ComponentBlockValue {
32
+ component: string
33
+ props: Record<string, unknown>
34
+ }
35
+
36
+ export interface ComponentBlockFieldProps {
37
+ label?: string
38
+ manifest: Manifest
39
+ /** Optional whitelist of component names. */
40
+ allow?: string[]
41
+ /** Default component picked when the field is first used. */
42
+ defaultComponent?: string
43
+ value?: ComponentBlockValue
44
+ onChange: (value: ComponentBlockValue) => void
45
+ /**
46
+ * Optional async validator hook. Receives the proposed value and the
47
+ * field config; should return `true` for ok or a string error.
48
+ * Falls back to a built-in structural validator when not provided —
49
+ * which is what cms-core would do server-side at save time anyway.
50
+ */
51
+ validate?: (value: ComponentBlockValue) => string | true
52
+ helpText?: string
53
+ }
54
+
55
+ export function ComponentBlockField({
56
+ label,
57
+ manifest,
58
+ allow,
59
+ defaultComponent,
60
+ value,
61
+ onChange,
62
+ validate,
63
+ helpText,
64
+ }: ComponentBlockFieldProps) {
65
+ const allowedComponents = useMemo(() => getAllowedComponents(manifest, allow), [manifest, allow])
66
+ // Track the initial seed application so we don't repeatedly stomp
67
+ // the editor's prop values on every render. The defaultComponent
68
+ // seed only fires once, when value is undefined on mount.
69
+ const seededRef = useRef(false)
70
+
71
+ useEffect(() => {
72
+ if (seededRef.current) return
73
+ if (value) {
74
+ seededRef.current = true
75
+ return
76
+ }
77
+ const picked =
78
+ allowedComponents.find((c) => c.name === defaultComponent) ?? allowedComponents[0]
79
+ if (!picked) return
80
+ seededRef.current = true
81
+ onChange({ component: picked.name, props: seedPropsForComponent(picked) })
82
+ }, [allowedComponents, defaultComponent, value, onChange])
83
+
84
+ const currentSpec =
85
+ (value && manifest.components.find((c) => c.name === value.component)) ?? allowedComponents[0]
86
+
87
+ // Pure structural validation when caller didn't provide their own.
88
+ // Keeps the form usable even without a live validator wired in.
89
+ const builtinValidate = useMemo(() => buildClientValidator(manifest, allow), [manifest, allow])
90
+ const validator = validate ?? builtinValidate
91
+
92
+ const [errorMessage, setErrorMessage] = useState<string | null>(null)
93
+ const perPropErrors = useMemo(() => parsePerPropErrors(errorMessage), [errorMessage])
94
+
95
+ useEffect(() => {
96
+ if (!value) {
97
+ setErrorMessage(null)
98
+ return
99
+ }
100
+ const result = validator(value)
101
+ setErrorMessage(result === true ? null : result)
102
+ }, [value, validator])
103
+
104
+ function handleComponentChange(componentName: string) {
105
+ const spec = manifest.components.find((c) => c.name === componentName)
106
+ if (!spec) return
107
+ onChange({ component: spec.name, props: seedPropsForComponent(spec) })
108
+ }
109
+
110
+ function handlePropChange(propName: string, next: unknown) {
111
+ if (!value) return
112
+ onChange({
113
+ ...value,
114
+ props: { ...value.props, [propName]: next },
115
+ })
116
+ }
117
+
118
+ if (!currentSpec) {
119
+ return (
120
+ <div className="rounded-md border border-[var(--destructive)] bg-red-50 p-3 text-sm text-[var(--destructive)]">
121
+ Component block has no components to choose from.
122
+ </div>
123
+ )
124
+ }
125
+
126
+ return (
127
+ <div className="space-y-3">
128
+ {label ? <label className="block text-sm font-medium">{label}</label> : null}
129
+
130
+ {allowedComponents.length > 1 ? (
131
+ <label className="block text-sm">
132
+ <div className="mb-1 text-xs tracking-wide text-[var(--muted-foreground)] uppercase">
133
+ Component
134
+ </div>
135
+ <select
136
+ value={currentSpec.name}
137
+ onChange={(e) => handleComponentChange(e.target.value)}
138
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
139
+ >
140
+ {allowedComponents.map((spec) => (
141
+ <option key={spec.name} value={spec.name}>
142
+ {spec.displayName}
143
+ </option>
144
+ ))}
145
+ </select>
146
+ </label>
147
+ ) : null}
148
+
149
+ {errorMessage ? (
150
+ <div className="rounded-md border border-[var(--destructive)] bg-red-50 px-3 py-2 text-xs text-[var(--destructive)]">
151
+ {errorMessage}
152
+ </div>
153
+ ) : null}
154
+
155
+ <fieldset className="rounded-md border border-[var(--border)] bg-[var(--card)] p-4">
156
+ <legend className="px-1 text-xs tracking-wide text-[var(--muted-foreground)] uppercase">
157
+ {currentSpec.displayName}
158
+ </legend>
159
+ {currentSpec.description ? (
160
+ <p className="mb-3 text-xs text-[var(--muted-foreground)]">{currentSpec.description}</p>
161
+ ) : null}
162
+ <div className="space-y-4">
163
+ {currentSpec.props.map((prop) => (
164
+ <PropInput
165
+ key={prop.name}
166
+ prop={prop}
167
+ value={value?.props?.[prop.name]}
168
+ onChange={(next) => handlePropChange(prop.name, next)}
169
+ errors={perPropErrors}
170
+ path={prop.name}
171
+ />
172
+ ))}
173
+ </div>
174
+ </fieldset>
175
+
176
+ {helpText ? <p className="text-xs text-[var(--muted-foreground)]">{helpText}</p> : null}
177
+ </div>
178
+ )
179
+ }
@@ -25,7 +25,7 @@ export function DateField({ label, value = '', onChange, required, helpText }: D
25
25
  />
26
26
  <button
27
27
  type="button"
28
- className="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--muted-foreground)]"
28
+ className="absolute top-1/2 right-2 -translate-y-1/2 text-[var(--muted-foreground)]"
29
29
  aria-label="Open calendar"
30
30
  >
31
31
  <svg
@@ -13,6 +13,7 @@ import { BlockBuilderField } from './BlockBuilderField.js'
13
13
  import { GroupField } from './GroupField.js'
14
14
  import { NavBuilderField } from './NavBuilderField.js'
15
15
  import { NumberField } from './NumberField.js'
16
+ import { ComponentBlockField } from './ComponentBlockField.js'
16
17
 
17
18
  export interface FieldDefinition {
18
19
  name: string
@@ -28,6 +29,9 @@ export interface FieldDefinition {
28
29
  multi?: boolean
29
30
  fields?: FieldDefinition[]
30
31
  blocks?: any[]
32
+ manifest?: unknown
33
+ allow?: string[]
34
+ defaultComponent?: string
31
35
  }
32
36
 
33
37
  export interface FieldRendererProps {
@@ -50,6 +54,7 @@ const FIELD_MAP: Record<string, React.ComponentType<any>> = {
50
54
  group: GroupField,
51
55
  nav: NavBuilderField,
52
56
  number: NumberField,
57
+ componentBlock: ComponentBlockField,
53
58
  }
54
59
 
55
60
  export function FieldRenderer({ field, value, onChange }: FieldRendererProps) {
@@ -79,6 +84,9 @@ export function FieldRenderer({ field, value, onChange }: FieldRendererProps) {
79
84
  fields={field.fields}
80
85
  blocks={field.blocks}
81
86
  name={field.name}
87
+ manifest={field.manifest}
88
+ allow={field.allow}
89
+ defaultComponent={field.defaultComponent}
82
90
  />
83
91
  )
84
92
  }