@actuate-media/cms-admin 0.9.0 → 0.11.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 (294) hide show
  1. package/dist/AdminRoot.d.ts.map +1 -1
  2. package/dist/AdminRoot.js +8 -5
  3. package/dist/AdminRoot.js.map +1 -1
  4. package/dist/__tests__/layout/primitives.test.d.ts +2 -0
  5. package/dist/__tests__/layout/primitives.test.d.ts.map +1 -0
  6. package/dist/__tests__/layout/primitives.test.js +34 -0
  7. package/dist/__tests__/layout/primitives.test.js.map +1 -0
  8. package/dist/__tests__/lib/cv.test.d.ts +2 -0
  9. package/dist/__tests__/lib/cv.test.d.ts.map +1 -0
  10. package/dist/__tests__/lib/cv.test.js +66 -0
  11. package/dist/__tests__/lib/cv.test.js.map +1 -0
  12. package/dist/actuate-admin.css +1 -1
  13. package/dist/assets/actuate-logo.d.ts +36 -0
  14. package/dist/assets/actuate-logo.d.ts.map +1 -0
  15. package/dist/assets/actuate-logo.js +15 -0
  16. package/dist/assets/actuate-logo.js.map +1 -0
  17. package/dist/components/Breadcrumbs.js +2 -2
  18. package/dist/components/CommandPalette.js +10 -10
  19. package/dist/components/ContentOverviewChart.js +3 -3
  20. package/dist/components/ErrorBoundary.js +1 -1
  21. package/dist/components/FocalPointPicker.js +2 -2
  22. package/dist/components/FolderTree.js +20 -20
  23. package/dist/components/LivePreview.js +3 -3
  24. package/dist/components/LocaleSwitcher.js +1 -1
  25. package/dist/components/MediaPickerModal.js +4 -4
  26. package/dist/components/PresenceIndicator.js +1 -1
  27. package/dist/components/SEOConfigPanel.d.ts +2 -0
  28. package/dist/components/SEOConfigPanel.d.ts.map +1 -0
  29. package/dist/components/SEOConfigPanel.js +174 -0
  30. package/dist/components/SEOConfigPanel.js.map +1 -0
  31. package/dist/components/SEOPanel.js +9 -9
  32. package/dist/components/SEOPerformance.js +2 -2
  33. package/dist/components/SchedulePublishDialog.d.ts +18 -0
  34. package/dist/components/SchedulePublishDialog.d.ts.map +1 -0
  35. package/dist/components/SchedulePublishDialog.js +106 -0
  36. package/dist/components/SchedulePublishDialog.js.map +1 -0
  37. package/dist/components/SharePreviewLinkDialog.d.ts +17 -0
  38. package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -0
  39. package/dist/components/SharePreviewLinkDialog.js +83 -0
  40. package/dist/components/SharePreviewLinkDialog.js.map +1 -0
  41. package/dist/components/TipTapEditor.js +5 -5
  42. package/dist/components/VersionHistory.js +2 -2
  43. package/dist/components/ui/Badge.d.ts +33 -3
  44. package/dist/components/ui/Badge.d.ts.map +1 -1
  45. package/dist/components/ui/Badge.js +42 -8
  46. package/dist/components/ui/Badge.js.map +1 -1
  47. package/dist/components/ui/Button.d.ts +19 -8
  48. package/dist/components/ui/Button.d.ts.map +1 -1
  49. package/dist/components/ui/Button.js +35 -14
  50. package/dist/components/ui/Button.js.map +1 -1
  51. package/dist/components/ui/Card.d.ts +26 -0
  52. package/dist/components/ui/Card.d.ts.map +1 -0
  53. package/dist/components/ui/Card.js +45 -0
  54. package/dist/components/ui/Card.js.map +1 -0
  55. package/dist/components/ui/DataTable.js +1 -1
  56. package/dist/components/ui/Input.d.ts +15 -0
  57. package/dist/components/ui/Input.d.ts.map +1 -0
  58. package/dist/components/ui/Input.js +23 -0
  59. package/dist/components/ui/Input.js.map +1 -0
  60. package/dist/components/ui/SearchInput.js +1 -1
  61. package/dist/components/ui/Select.d.ts +16 -0
  62. package/dist/components/ui/Select.d.ts.map +1 -0
  63. package/dist/components/ui/Select.js +25 -0
  64. package/dist/components/ui/Select.js.map +1 -0
  65. package/dist/components/ui/Toast.js +1 -1
  66. package/dist/components/ui/index.d.ts +10 -4
  67. package/dist/components/ui/index.d.ts.map +1 -1
  68. package/dist/components/ui/index.js +5 -2
  69. package/dist/components/ui/index.js.map +1 -1
  70. package/dist/fields/BlockBuilderField.js +3 -3
  71. package/dist/fields/DateField.js +1 -1
  72. package/dist/fields/RelationshipField.js +3 -3
  73. package/dist/fields/TextField.js +1 -1
  74. package/dist/index.d.ts +6 -0
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/layout/Header.js +1 -1
  79. package/dist/layout/Layout.d.ts +14 -0
  80. package/dist/layout/Layout.d.ts.map +1 -1
  81. package/dist/layout/Layout.js +17 -11
  82. package/dist/layout/Layout.js.map +1 -1
  83. package/dist/layout/Sidebar.d.ts.map +1 -1
  84. package/dist/layout/Sidebar.js +21 -11
  85. package/dist/layout/Sidebar.js.map +1 -1
  86. package/dist/layout/primitives/AdminShell.d.ts +43 -0
  87. package/dist/layout/primitives/AdminShell.d.ts.map +1 -0
  88. package/dist/layout/primitives/AdminShell.js +51 -0
  89. package/dist/layout/primitives/AdminShell.js.map +1 -0
  90. package/dist/layout/primitives/Box.d.ts +19 -0
  91. package/dist/layout/primitives/Box.d.ts.map +1 -0
  92. package/dist/layout/primitives/Box.js +12 -0
  93. package/dist/layout/primitives/Box.js.map +1 -0
  94. package/dist/layout/primitives/Cluster.d.ts +27 -0
  95. package/dist/layout/primitives/Cluster.d.ts.map +1 -0
  96. package/dist/layout/primitives/Cluster.js +37 -0
  97. package/dist/layout/primitives/Cluster.js.map +1 -0
  98. package/dist/layout/primitives/Grid.d.ts +45 -0
  99. package/dist/layout/primitives/Grid.d.ts.map +1 -0
  100. package/dist/layout/primitives/Grid.js +59 -0
  101. package/dist/layout/primitives/Grid.js.map +1 -0
  102. package/dist/layout/primitives/PageContainer.d.ts +36 -0
  103. package/dist/layout/primitives/PageContainer.d.ts.map +1 -0
  104. package/dist/layout/primitives/PageContainer.js +41 -0
  105. package/dist/layout/primitives/PageContainer.js.map +1 -0
  106. package/dist/layout/primitives/Split.d.ts +34 -0
  107. package/dist/layout/primitives/Split.d.ts.map +1 -0
  108. package/dist/layout/primitives/Split.js +27 -0
  109. package/dist/layout/primitives/Split.js.map +1 -0
  110. package/dist/layout/primitives/Stack.d.ts +23 -0
  111. package/dist/layout/primitives/Stack.d.ts.map +1 -0
  112. package/dist/layout/primitives/Stack.js +34 -0
  113. package/dist/layout/primitives/Stack.js.map +1 -0
  114. package/dist/layout/primitives/index.d.ts +30 -0
  115. package/dist/layout/primitives/index.d.ts.map +1 -0
  116. package/dist/layout/primitives/index.js +22 -0
  117. package/dist/layout/primitives/index.js.map +1 -0
  118. package/dist/layout/primitives/tokens.d.ts +48 -0
  119. package/dist/layout/primitives/tokens.d.ts.map +1 -0
  120. package/dist/layout/primitives/tokens.js +54 -0
  121. package/dist/layout/primitives/tokens.js.map +1 -0
  122. package/dist/lib/cv.d.ts +53 -0
  123. package/dist/lib/cv.d.ts.map +1 -0
  124. package/dist/lib/cv.js +39 -0
  125. package/dist/lib/cv.js.map +1 -0
  126. package/dist/views/ApiKeys.d.ts.map +1 -1
  127. package/dist/views/ApiKeys.js +13 -11
  128. package/dist/views/ApiKeys.js.map +1 -1
  129. package/dist/views/CollectionList.js +8 -8
  130. package/dist/views/Dashboard.d.ts.map +1 -1
  131. package/dist/views/Dashboard.js +333 -78
  132. package/dist/views/Dashboard.js.map +1 -1
  133. package/dist/views/DocumentEdit.d.ts.map +1 -1
  134. package/dist/views/DocumentEdit.js +17 -5
  135. package/dist/views/DocumentEdit.js.map +1 -1
  136. package/dist/views/ForgotPassword.js +2 -2
  137. package/dist/views/FormEditor.js +5 -5
  138. package/dist/views/FormSubmissions.js +6 -6
  139. package/dist/views/Forms.js +2 -2
  140. package/dist/views/Login.d.ts +16 -1
  141. package/dist/views/Login.d.ts.map +1 -1
  142. package/dist/views/Login.js +17 -7
  143. package/dist/views/Login.js.map +1 -1
  144. package/dist/views/MediaBrowser.js +16 -16
  145. package/dist/views/PageEditor.js +2 -2
  146. package/dist/views/Pages.js +10 -10
  147. package/dist/views/PostEditor.js +2 -2
  148. package/dist/views/Posts.js +4 -4
  149. package/dist/views/Redirects.js +4 -4
  150. package/dist/views/ResetPassword.js +2 -2
  151. package/dist/views/SEO.js +6 -6
  152. package/dist/views/ScriptTagEditor.js +4 -4
  153. package/dist/views/ScriptTags.js +2 -2
  154. package/dist/views/Settings.d.ts.map +1 -1
  155. package/dist/views/Settings.js +9 -8
  156. package/dist/views/Settings.js.map +1 -1
  157. package/dist/views/SetupWizard.js +2 -2
  158. package/dist/views/Users.js +4 -4
  159. package/dist/views/page-builder/AIBlockAssist.js +1 -1
  160. package/dist/views/page-builder/AIGenerateDialog.js +10 -10
  161. package/dist/views/page-builder/BlockEditor.js +10 -10
  162. package/dist/views/page-builder/BlockPicker.js +4 -4
  163. package/dist/views/page-builder/BottomBar.js +1 -1
  164. package/dist/views/page-builder/BuilderToolbar.js +2 -2
  165. package/dist/views/page-builder/ContextPanel.js +2 -2
  166. package/dist/views/page-builder/DesignScore.js +9 -9
  167. package/dist/views/page-builder/NodeSettings.js +8 -8
  168. package/dist/views/page-builder/PageBuilder.js +3 -3
  169. package/dist/views/page-builder/PageSettings.js +1 -1
  170. package/dist/views/page-builder/PageTemplates.js +2 -2
  171. package/dist/views/page-builder/SEOPanel.js +13 -13
  172. package/dist/views/page-builder/SavedSections.js +5 -5
  173. package/dist/views/page-builder/TemplatePicker.js +2 -2
  174. package/dist/views/page-builder/block-renderers/CTAPreview.js +5 -5
  175. package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
  176. package/dist/views/page-builder/block-renderers/CodePreview.js +1 -1
  177. package/dist/views/page-builder/block-renderers/FAQPreview.js +3 -3
  178. package/dist/views/page-builder/block-renderers/FallbackPreview.js +1 -1
  179. package/dist/views/page-builder/block-renderers/FormPreview.js +3 -3
  180. package/dist/views/page-builder/block-renderers/GalleryPreview.js +5 -5
  181. package/dist/views/page-builder/block-renderers/HeroPreview.js +3 -3
  182. package/dist/views/page-builder/block-renderers/ImagePreview.js +3 -3
  183. package/dist/views/page-builder/block-renderers/TextPreview.js +3 -3
  184. package/dist/views/page-builder/block-renderers/VideoPreview.js +4 -4
  185. package/dist/views/page-builder/canvas/BlockRenderer.js +1 -1
  186. package/dist/views/page-builder/canvas/BuilderCanvas.js +3 -3
  187. package/dist/views/page-builder/canvas/ColumnRenderer.js +2 -2
  188. package/dist/views/page-builder/canvas/ContainerRenderer.js +2 -2
  189. package/dist/views/page-builder/canvas/RowRenderer.js +2 -2
  190. package/dist/views/page-builder/canvas/SectionRenderer.js +2 -2
  191. package/package.json +6 -2
  192. package/src/AdminRoot.tsx +21 -11
  193. package/src/__tests__/layout/primitives.test.ts +37 -0
  194. package/src/__tests__/lib/cv.test.ts +74 -0
  195. package/src/assets/actuate-logo.tsx +72 -0
  196. package/src/components/Breadcrumbs.tsx +6 -6
  197. package/src/components/CommandPalette.tsx +34 -34
  198. package/src/components/ContentOverviewChart.tsx +3 -3
  199. package/src/components/ErrorBoundary.tsx +3 -3
  200. package/src/components/FocalPointPicker.tsx +4 -4
  201. package/src/components/FolderTree.tsx +38 -38
  202. package/src/components/LivePreview.tsx +16 -16
  203. package/src/components/LocaleSwitcher.tsx +7 -7
  204. package/src/components/MediaPickerModal.tsx +21 -21
  205. package/src/components/PresenceIndicator.tsx +2 -2
  206. package/src/components/SEOConfigPanel.tsx +582 -0
  207. package/src/components/SEOPanel.tsx +46 -46
  208. package/src/components/SEOPerformance.tsx +21 -21
  209. package/src/components/SchedulePublishDialog.tsx +241 -0
  210. package/src/components/SharePreviewLinkDialog.tsx +227 -0
  211. package/src/components/TipTapEditor.tsx +33 -33
  212. package/src/components/VersionHistory.tsx +16 -16
  213. package/src/components/ui/Badge.tsx +66 -14
  214. package/src/components/ui/Button.tsx +70 -33
  215. package/src/components/ui/Card.tsx +101 -0
  216. package/src/components/ui/DataTable.tsx +1 -1
  217. package/src/components/ui/Input.tsx +35 -0
  218. package/src/components/ui/SearchInput.tsx +4 -4
  219. package/src/components/ui/Select.tsx +56 -0
  220. package/src/components/ui/Toast.tsx +1 -1
  221. package/src/components/ui/index.ts +18 -4
  222. package/src/fields/BlockBuilderField.tsx +3 -3
  223. package/src/fields/DateField.tsx +1 -1
  224. package/src/fields/RelationshipField.tsx +10 -10
  225. package/src/fields/TextField.tsx +1 -1
  226. package/src/index.ts +32 -0
  227. package/src/layout/Header.tsx +28 -28
  228. package/src/layout/Layout.tsx +39 -46
  229. package/src/layout/Sidebar.tsx +37 -64
  230. package/src/layout/primitives/AdminShell.tsx +118 -0
  231. package/src/layout/primitives/Box.tsx +30 -0
  232. package/src/layout/primitives/Cluster.tsx +74 -0
  233. package/src/layout/primitives/Grid.tsx +120 -0
  234. package/src/layout/primitives/PageContainer.tsx +96 -0
  235. package/src/layout/primitives/Split.tsx +73 -0
  236. package/src/layout/primitives/Stack.tsx +67 -0
  237. package/src/layout/primitives/index.ts +36 -0
  238. package/src/layout/primitives/tokens.ts +76 -0
  239. package/src/lib/cv.ts +96 -0
  240. package/src/styles/build-input.css +1 -1
  241. package/src/views/ApiKeys.tsx +57 -57
  242. package/src/views/CollectionList.tsx +30 -30
  243. package/src/views/Dashboard.tsx +737 -186
  244. package/src/views/DocumentEdit.tsx +90 -10
  245. package/src/views/ForgotPassword.tsx +18 -18
  246. package/src/views/FormEditor.tsx +75 -75
  247. package/src/views/FormSubmissions.tsx +76 -76
  248. package/src/views/Forms.tsx +27 -27
  249. package/src/views/Login.tsx +65 -25
  250. package/src/views/MediaBrowser.tsx +127 -127
  251. package/src/views/PageEditor.tsx +25 -25
  252. package/src/views/Pages.tsx +59 -59
  253. package/src/views/PostEditor.tsx +37 -37
  254. package/src/views/Posts.tsx +48 -48
  255. package/src/views/Redirects.tsx +21 -21
  256. package/src/views/ResetPassword.tsx +28 -28
  257. package/src/views/SEO.tsx +144 -144
  258. package/src/views/ScriptTagEditor.tsx +24 -24
  259. package/src/views/ScriptTags.tsx +10 -10
  260. package/src/views/Settings.tsx +88 -80
  261. package/src/views/SetupWizard.tsx +28 -28
  262. package/src/views/Users.tsx +20 -20
  263. package/src/views/page-builder/AIBlockAssist.tsx +1 -1
  264. package/src/views/page-builder/AIGenerateDialog.tsx +63 -63
  265. package/src/views/page-builder/BlockEditor.tsx +26 -26
  266. package/src/views/page-builder/BlockPicker.tsx +22 -22
  267. package/src/views/page-builder/BottomBar.tsx +8 -8
  268. package/src/views/page-builder/BuilderToolbar.tsx +17 -17
  269. package/src/views/page-builder/ContextPanel.tsx +3 -3
  270. package/src/views/page-builder/DesignScore.tsx +21 -21
  271. package/src/views/page-builder/NodeSettings.tsx +27 -27
  272. package/src/views/page-builder/PageBuilder.tsx +11 -11
  273. package/src/views/page-builder/PageSettings.tsx +4 -4
  274. package/src/views/page-builder/PageTemplates.tsx +18 -18
  275. package/src/views/page-builder/SEOPanel.tsx +53 -53
  276. package/src/views/page-builder/SavedSections.tsx +37 -37
  277. package/src/views/page-builder/TemplatePicker.tsx +17 -17
  278. package/src/views/page-builder/block-renderers/CTAPreview.tsx +13 -13
  279. package/src/views/page-builder/block-renderers/CardsPreview.tsx +5 -5
  280. package/src/views/page-builder/block-renderers/CodePreview.tsx +6 -6
  281. package/src/views/page-builder/block-renderers/FAQPreview.tsx +13 -13
  282. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +3 -3
  283. package/src/views/page-builder/block-renderers/FormPreview.tsx +20 -20
  284. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +8 -8
  285. package/src/views/page-builder/block-renderers/HeroPreview.tsx +16 -16
  286. package/src/views/page-builder/block-renderers/ImagePreview.tsx +4 -4
  287. package/src/views/page-builder/block-renderers/TextPreview.tsx +14 -14
  288. package/src/views/page-builder/block-renderers/VideoPreview.tsx +12 -12
  289. package/src/views/page-builder/canvas/BlockRenderer.tsx +4 -4
  290. package/src/views/page-builder/canvas/BuilderCanvas.tsx +6 -6
  291. package/src/views/page-builder/canvas/ColumnRenderer.tsx +3 -3
  292. package/src/views/page-builder/canvas/ContainerRenderer.tsx +2 -2
  293. package/src/views/page-builder/canvas/RowRenderer.tsx +2 -2
  294. package/src/views/page-builder/canvas/SectionRenderer.tsx +2 -2
@@ -0,0 +1,120 @@
1
+ import type { HTMLAttributes, ReactNode, ElementType } from 'react'
2
+ import type { SpaceToken } from './tokens.js'
3
+
4
+ /**
5
+ * Responsive column count. Pass a number for static grids, or a map to
6
+ * change column count at each breakpoint.
7
+ *
8
+ * @example
9
+ * columns={3} // 3 cols at every breakpoint
10
+ * columns={{ base: 1, md: 2, lg: 4 }} // responsive
11
+ */
12
+ export type GridResponsive =
13
+ | number
14
+ | { base?: number; sm?: number; md?: number; lg?: number; xl?: number; '2xl'?: number }
15
+
16
+ export interface GridProps extends HTMLAttributes<HTMLElement> {
17
+ columns: GridResponsive
18
+ /** Gap between cells. */
19
+ space?: SpaceToken
20
+ /** Row gap (defaults to `space`). */
21
+ rowSpace?: SpaceToken
22
+ /** Render as a different element. Defaults to `div`. */
23
+ as?: ElementType
24
+ /**
25
+ * Use auto-fit instead of explicit column counts. When set, `minItemWidth`
26
+ * controls the minimum tile width before the grid reflows. Use this for
27
+ * card grids that should adapt to container width without specific
28
+ * breakpoints.
29
+ */
30
+ autoFit?: boolean
31
+ /** Min tile width when `autoFit` is true. Defaults to `16rem`. */
32
+ minItemWidth?: string
33
+ children?: ReactNode
34
+ }
35
+
36
+ const COL_CLASS: Record<number, string> = {
37
+ 1: 'grid-cols-1',
38
+ 2: 'grid-cols-2',
39
+ 3: 'grid-cols-3',
40
+ 4: 'grid-cols-4',
41
+ 5: 'grid-cols-5',
42
+ 6: 'grid-cols-6',
43
+ 7: 'grid-cols-7',
44
+ 8: 'grid-cols-8',
45
+ 9: 'grid-cols-9',
46
+ 10: 'grid-cols-10',
47
+ 11: 'grid-cols-11',
48
+ 12: 'grid-cols-12',
49
+ }
50
+
51
+ const BP_PREFIX = ['', 'sm:', 'md:', 'lg:', 'xl:', '2xl:'] as const
52
+ type BpKey = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
53
+
54
+ function columnsToClasses(columns: GridResponsive): string {
55
+ if (typeof columns === 'number') {
56
+ return COL_CLASS[columns] ?? 'grid-cols-1'
57
+ }
58
+ const order: BpKey[] = ['base', 'sm', 'md', 'lg', 'xl', '2xl']
59
+ return order
60
+ .map((bp, i) => {
61
+ const n = columns[bp]
62
+ if (!n) return ''
63
+ const base = COL_CLASS[n] ?? 'grid-cols-1'
64
+ return BP_PREFIX[i] + base
65
+ })
66
+ .filter(Boolean)
67
+ .join(' ')
68
+ }
69
+
70
+ /**
71
+ * Responsive CSS grid primitive. Pass either an explicit `columns` count
72
+ * (per-breakpoint or static) or set `autoFit` for a `repeat(auto-fit, …)`
73
+ * tile grid. Both modes share the same `space` / `rowSpace` knobs so
74
+ * usage is consistent across views.
75
+ */
76
+ export function Grid({
77
+ columns,
78
+ space = '4',
79
+ rowSpace,
80
+ as: Component = 'div',
81
+ autoFit,
82
+ minItemWidth = '16rem',
83
+ className = '',
84
+ style,
85
+ children,
86
+ ...rest
87
+ }: GridProps) {
88
+ if (autoFit) {
89
+ return (
90
+ <Component
91
+ className={['grid', `gap-${space}`, rowSpace ? `gap-y-${rowSpace}` : '', className]
92
+ .filter(Boolean)
93
+ .join(' ')}
94
+ style={{
95
+ gridTemplateColumns: `repeat(auto-fit, minmax(${minItemWidth}, 1fr))`,
96
+ ...style,
97
+ }}
98
+ {...rest}
99
+ >
100
+ {children}
101
+ </Component>
102
+ )
103
+ }
104
+
105
+ const classes = [
106
+ 'grid',
107
+ columnsToClasses(columns),
108
+ `gap-${space}`,
109
+ rowSpace ? `gap-y-${rowSpace}` : '',
110
+ className,
111
+ ]
112
+ .filter(Boolean)
113
+ .join(' ')
114
+
115
+ return (
116
+ <Component className={classes} style={style} {...rest}>
117
+ {children}
118
+ </Component>
119
+ )
120
+ }
@@ -0,0 +1,96 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react'
2
+ import { Stack } from './Stack.js'
3
+
4
+ export interface PageContainerProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
5
+ /** Page title rendered as an h1 inside the page header. */
6
+ title?: ReactNode
7
+ /** Optional sub-headline shown beneath the title. */
8
+ description?: ReactNode
9
+ /** Right-aligned action area (buttons, filters). Rendered in the header. */
10
+ actions?: ReactNode
11
+ /**
12
+ * Maximum width of the content. Defaults to `7xl` (Tailwind's max-w-7xl,
13
+ * ~1280px). Pass `'full'` for edge-to-edge content.
14
+ */
15
+ maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | 'full'
16
+ /** Horizontal padding token. Defaults to `6` (24px). */
17
+ paddingX?: '4' | '6' | '8'
18
+ /** Vertical padding token. Defaults to `6`. */
19
+ paddingY?: '4' | '6' | '8' | '10'
20
+ /** Vertical gap between the header and the body. Defaults to `6`. */
21
+ gap?: '4' | '6' | '8'
22
+ children?: ReactNode
23
+ }
24
+
25
+ const MAX_WIDTH_CLASS: Record<NonNullable<PageContainerProps['maxWidth']>, string> = {
26
+ sm: 'max-w-sm',
27
+ md: 'max-w-md',
28
+ lg: 'max-w-lg',
29
+ xl: 'max-w-xl',
30
+ '2xl': 'max-w-2xl',
31
+ '3xl': 'max-w-3xl',
32
+ '4xl': 'max-w-4xl',
33
+ '5xl': 'max-w-5xl',
34
+ '6xl': 'max-w-6xl',
35
+ '7xl': 'max-w-7xl',
36
+ full: 'max-w-none',
37
+ }
38
+
39
+ /**
40
+ * The single canonical wrapper for every admin page. PageContainer owns
41
+ * three responsibilities so individual views can stop redoing them:
42
+ *
43
+ * 1. Consistent max-width + horizontal padding (so dashboards, lists, and
44
+ * forms all line up visually).
45
+ * 2. A standard header slot with title + description + actions.
46
+ * 3. A standard body slot rendered as a `<Stack>` so children stack with
47
+ * consistent vertical rhythm.
48
+ *
49
+ * Use PageContainer at the top of every screen. Compose with `<Stack>` /
50
+ * `<Grid>` / `<Cluster>` inside. Never roll a per-screen wrapper.
51
+ */
52
+ export function PageContainer({
53
+ title,
54
+ description,
55
+ actions,
56
+ maxWidth = '7xl',
57
+ paddingX = '6',
58
+ paddingY = '6',
59
+ gap = '6',
60
+ className = '',
61
+ children,
62
+ ...rest
63
+ }: PageContainerProps) {
64
+ const outerClasses = [
65
+ 'mx-auto w-full',
66
+ MAX_WIDTH_CLASS[maxWidth],
67
+ `px-${paddingX}`,
68
+ `py-${paddingY}`,
69
+ className,
70
+ ]
71
+ .filter(Boolean)
72
+ .join(' ')
73
+
74
+ return (
75
+ <section className={outerClasses} {...rest}>
76
+ <Stack space={gap}>
77
+ {(title || description || actions) && (
78
+ <header className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
79
+ {(title || description) && (
80
+ <div className="min-w-0">
81
+ {title && (
82
+ <h1 className="text-foreground truncate text-2xl font-semibold tracking-tight">
83
+ {title}
84
+ </h1>
85
+ )}
86
+ {description && <p className="text-muted-foreground mt-1 text-sm">{description}</p>}
87
+ </div>
88
+ )}
89
+ {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
90
+ </header>
91
+ )}
92
+ {children}
93
+ </Stack>
94
+ </section>
95
+ )
96
+ }
@@ -0,0 +1,73 @@
1
+ import type { HTMLAttributes, ReactNode, ElementType } from 'react'
2
+ import type { SpaceToken } from './tokens.js'
3
+
4
+ export interface SplitProps extends HTMLAttributes<HTMLElement> {
5
+ /**
6
+ * Fraction of the container the primary pane should take, expressed as
7
+ * a CSS fraction string. Defaults to `'2fr 1fr'` (primary 2x sidebar).
8
+ *
9
+ * @example
10
+ * <Split fraction="3fr 1fr"> // 75% / 25%
11
+ * <Split fraction="minmax(0, 1fr) 320px"> // fluid + fixed sidebar
12
+ */
13
+ fraction?: string
14
+ /** Gap between the two panes. */
15
+ space?: SpaceToken
16
+ /**
17
+ * Stack vertically at this breakpoint and below. Pass `false` to never
18
+ * stack. Defaults to `md` (stacks on screens narrower than 768px).
19
+ */
20
+ stackBelow?: 'sm' | 'md' | 'lg' | 'xl' | false
21
+ /** Reverse the order of children (sidebar first instead of second). */
22
+ reverse?: boolean
23
+ /** Render as a different element. */
24
+ as?: ElementType
25
+ children: [ReactNode, ReactNode]
26
+ }
27
+
28
+ const STACK_BELOW_CLASS = {
29
+ sm: 'sm:grid',
30
+ md: 'md:grid',
31
+ lg: 'lg:grid',
32
+ xl: 'xl:grid',
33
+ } as const
34
+
35
+ /**
36
+ * Two-pane split layout (primary + sidebar/aside) that collapses to a
37
+ * stacked layout below a configurable breakpoint. Use this for the
38
+ * document editor (`<Split fraction="minmax(0, 1fr) 320px">`), settings
39
+ * pages with a navigation rail, etc. — anywhere two panes need to live
40
+ * side-by-side at desktop widths and stack on mobile.
41
+ */
42
+ export function Split({
43
+ fraction = '2fr 1fr',
44
+ space = '6',
45
+ stackBelow = 'md',
46
+ reverse,
47
+ as: Component = 'div',
48
+ className = '',
49
+ style,
50
+ children,
51
+ ...rest
52
+ }: SplitProps) {
53
+ const stackClass =
54
+ stackBelow === false ? 'grid' : `flex flex-col ${STACK_BELOW_CLASS[stackBelow]}`
55
+ const classes = [stackClass, `gap-${space}`, reverse ? 'flex-col-reverse' : '', className]
56
+ .filter(Boolean)
57
+ .join(' ')
58
+
59
+ return (
60
+ <Component
61
+ className={classes}
62
+ style={{
63
+ ...(stackBelow === false
64
+ ? { gridTemplateColumns: fraction }
65
+ : { gridTemplateColumns: fraction }),
66
+ ...style,
67
+ }}
68
+ {...rest}
69
+ >
70
+ {children}
71
+ </Component>
72
+ )
73
+ }
@@ -0,0 +1,67 @@
1
+ import type { HTMLAttributes, ReactNode, ElementType } from 'react'
2
+ import type { SpaceToken } from './tokens.js'
3
+
4
+ export type StackSpace = SpaceToken
5
+
6
+ export interface StackProps extends HTMLAttributes<HTMLElement> {
7
+ /** Vertical gap between children. Maps to Tailwind `gap-{space}`. */
8
+ space?: StackSpace
9
+ /** Render as a different element (e.g. `<section>`, `<article>`). Defaults to `div`. */
10
+ as?: ElementType
11
+ /** Center children horizontally (sets `items-center`). */
12
+ align?: 'start' | 'center' | 'end' | 'stretch'
13
+ /** Distribute vertical space (sets `justify-{value}`). */
14
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
15
+ /** Stretch the stack to fill its parent's height. */
16
+ fill?: boolean
17
+ children?: ReactNode
18
+ }
19
+
20
+ const ALIGN_CLASS: Record<NonNullable<StackProps['align']>, string> = {
21
+ start: 'items-start',
22
+ center: 'items-center',
23
+ end: 'items-end',
24
+ stretch: 'items-stretch',
25
+ }
26
+
27
+ const JUSTIFY_CLASS: Record<NonNullable<StackProps['justify']>, string> = {
28
+ start: 'justify-start',
29
+ center: 'justify-center',
30
+ end: 'justify-end',
31
+ between: 'justify-between',
32
+ around: 'justify-around',
33
+ evenly: 'justify-evenly',
34
+ }
35
+
36
+ /**
37
+ * Vertical stack with a consistent gap between children. This is the
38
+ * single sanctioned way to space block-level elements in the admin —
39
+ * never reach for `space-y-*` directly.
40
+ */
41
+ export function Stack({
42
+ space = '4',
43
+ as: Component = 'div',
44
+ align,
45
+ justify,
46
+ fill,
47
+ className = '',
48
+ children,
49
+ ...rest
50
+ }: StackProps) {
51
+ const classes = [
52
+ 'flex flex-col',
53
+ `gap-${space}`,
54
+ align ? ALIGN_CLASS[align] : '',
55
+ justify ? JUSTIFY_CLASS[justify] : '',
56
+ fill ? 'h-full' : '',
57
+ className,
58
+ ]
59
+ .filter(Boolean)
60
+ .join(' ')
61
+
62
+ return (
63
+ <Component className={classes} {...rest}>
64
+ {children}
65
+ </Component>
66
+ )
67
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Layout primitives for the Actuate admin.
3
+ *
4
+ * These exist so views never hand-roll their own flex/grid scaffolding.
5
+ * Every admin view should compose these primitives instead of writing raw
6
+ * Tailwind classes for spacing/alignment/structure. That contract is what
7
+ * keeps layout bugs (sidebar overlap, breakpoint mismatches, missing
8
+ * `min-w-0`) from recurring in new views.
9
+ *
10
+ * Naming follows the "Every Layout" canon
11
+ * (https://every-layout.dev/) plus our own `AdminShell` and
12
+ * `PageContainer` for the admin-specific outer chrome.
13
+ */
14
+ export { AdminShell } from './AdminShell.js'
15
+ export type { AdminShellProps } from './AdminShell.js'
16
+
17
+ export { PageContainer } from './PageContainer.js'
18
+ export type { PageContainerProps } from './PageContainer.js'
19
+
20
+ export { Stack } from './Stack.js'
21
+ export type { StackProps, StackSpace } from './Stack.js'
22
+
23
+ export { Cluster } from './Cluster.js'
24
+ export type { ClusterProps, ClusterAlign, ClusterJustify } from './Cluster.js'
25
+
26
+ export { Grid } from './Grid.js'
27
+ export type { GridProps, GridResponsive } from './Grid.js'
28
+
29
+ export { Split } from './Split.js'
30
+ export type { SplitProps } from './Split.js'
31
+
32
+ export { Box } from './Box.js'
33
+ export type { BoxProps } from './Box.js'
34
+
35
+ export { tokens } from './tokens.js'
36
+ export type { SpaceToken, RadiusToken } from './tokens.js'
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Design tokens — single source of truth for the admin design system.
3
+ *
4
+ * Tokens are surfaced both as TypeScript constants (for inline-style escape
5
+ * hatches) and via CSS custom properties in `styles/theme.css`. Always
6
+ * prefer the CSS variables in production code; the TS constants are for
7
+ * computed inline styles only (e.g. dynamic widths, computed grid sizes).
8
+ */
9
+
10
+ export type SpaceToken =
11
+ | '0'
12
+ | 'px'
13
+ | '0.5'
14
+ | '1'
15
+ | '1.5'
16
+ | '2'
17
+ | '2.5'
18
+ | '3'
19
+ | '4'
20
+ | '5'
21
+ | '6'
22
+ | '8'
23
+ | '10'
24
+ | '12'
25
+ | '16'
26
+
27
+ export type RadiusToken = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'
28
+
29
+ /** Numeric pixel values for inline style escape hatches. */
30
+ export const tokens = {
31
+ // Spacing scale matches Tailwind's default (--spacing = 0.25rem = 4px).
32
+ space: {
33
+ '0': 0,
34
+ px: 1,
35
+ '0.5': 2,
36
+ '1': 4,
37
+ '1.5': 6,
38
+ '2': 8,
39
+ '2.5': 10,
40
+ '3': 12,
41
+ '4': 16,
42
+ '5': 20,
43
+ '6': 24,
44
+ '8': 32,
45
+ '10': 40,
46
+ '12': 48,
47
+ '16': 64,
48
+ } as const satisfies Record<SpaceToken, number>,
49
+
50
+ radius: {
51
+ none: 0,
52
+ sm: 2,
53
+ md: 6,
54
+ lg: 8,
55
+ xl: 12,
56
+ full: 9999,
57
+ } as const satisfies Record<RadiusToken, number>,
58
+
59
+ // Breakpoints mirror Tailwind defaults so primitives can compose with
60
+ // utility classes without surprises. Use `matchMedia(`(min-width: …`)` to
61
+ // sync JS-driven layout decisions with these values.
62
+ breakpoint: {
63
+ sm: 640,
64
+ md: 768,
65
+ lg: 1024,
66
+ xl: 1280,
67
+ '2xl': 1536,
68
+ } as const,
69
+ }
70
+
71
+ /** Map a space token to the corresponding Tailwind `space-y-*` / `gap-*` value. */
72
+ export function spaceToTw(t: SpaceToken): string {
73
+ // Tailwind's spacing utility already accepts the same scale, so we map
74
+ // 1:1. Centralizing here means callers don't repeat the string literal.
75
+ return t
76
+ }
package/src/lib/cv.ts ADDED
@@ -0,0 +1,96 @@
1
+ import clsx from 'clsx'
2
+
3
+ /**
4
+ * Tiny in-house implementation of the `class-variance-authority` pattern
5
+ * (a.k.a. `cva` / `tailwind-variants`). We roll our own to avoid a runtime
6
+ * dependency while keeping the API surface compatible with the shadcn /
7
+ * cva ecosystem — anyone familiar with cva can read these calls.
8
+ *
9
+ * @example
10
+ * const button = cv('inline-flex items-center', {
11
+ * variants: {
12
+ * intent: {
13
+ * primary: 'bg-primary text-white',
14
+ * secondary: 'bg-secondary text-foreground',
15
+ * },
16
+ * size: {
17
+ * sm: 'px-2 py-1 text-xs',
18
+ * md: 'px-4 py-2 text-sm',
19
+ * },
20
+ * },
21
+ * defaultVariants: { intent: 'primary', size: 'md' },
22
+ * })
23
+ *
24
+ * button({ intent: 'secondary' }) // -> "inline-flex … bg-secondary …"
25
+ * button({ intent: 'secondary', class: 'shadow' }) // append extras
26
+ */
27
+
28
+ type ClassValue = string | undefined | false | null
29
+
30
+ export type VariantsConfig = Record<string, Record<string, ClassValue>>
31
+
32
+ type VariantSelection<V extends VariantsConfig> = {
33
+ [K in keyof V]?: keyof V[K]
34
+ }
35
+
36
+ interface CompoundVariant<V extends VariantsConfig> {
37
+ // Subset of variants that must match for the compound classes to apply.
38
+ // Use string values from each variant key (or arrays for "match any").
39
+ [key: string]: keyof V[keyof V] | Array<keyof V[keyof V]> | ClassValue
40
+ class?: ClassValue
41
+ }
42
+
43
+ export interface CvConfig<V extends VariantsConfig> {
44
+ variants: V
45
+ defaultVariants?: VariantSelection<V>
46
+ compoundVariants?: Array<CompoundVariant<V>>
47
+ }
48
+
49
+ export type VariantProps<F extends (...args: any) => any> = Omit<
50
+ NonNullable<Parameters<F>[0]>,
51
+ 'class' | 'className'
52
+ >
53
+
54
+ /**
55
+ * Build a variant-aware class generator.
56
+ *
57
+ * The returned function accepts the variant selection plus an optional
58
+ * `class` / `className` escape hatch and returns the combined Tailwind
59
+ * class string. Compound variants run after the base + per-variant
60
+ * classes, so they take precedence when the Tailwind ordering matters.
61
+ */
62
+ export function cv<V extends VariantsConfig>(base: ClassValue, config: CvConfig<V>) {
63
+ return function variantClass(
64
+ props?: VariantSelection<V> & { class?: ClassValue; className?: ClassValue },
65
+ ): string {
66
+ const selected = { ...config.defaultVariants, ...props } as Record<string, unknown>
67
+
68
+ const variantClasses: ClassValue[] = []
69
+ for (const [key, valuesMap] of Object.entries(config.variants)) {
70
+ const value = selected[key]
71
+ if (value === undefined || value === null) continue
72
+ const cls = valuesMap[value as string]
73
+ if (cls) variantClasses.push(cls)
74
+ }
75
+
76
+ const compoundClasses: ClassValue[] = []
77
+ if (config.compoundVariants) {
78
+ for (const compound of config.compoundVariants) {
79
+ const { class: cClass, className: _cn, ...match } = compound as any
80
+ const matches = Object.entries(match).every(([k, v]) => {
81
+ const current = selected[k]
82
+ if (Array.isArray(v)) return (v as unknown[]).includes(current)
83
+ return current === v
84
+ })
85
+ if (matches && cClass) compoundClasses.push(cClass as ClassValue)
86
+ }
87
+ }
88
+
89
+ return clsx(
90
+ base,
91
+ ...variantClasses,
92
+ ...compoundClasses,
93
+ (props?.class ?? props?.className) as ClassValue,
94
+ )
95
+ }
96
+ }
@@ -7,5 +7,5 @@
7
7
  * import '@actuate-media/cms-admin/styles/precompiled.css'
8
8
  */
9
9
  @import 'tailwindcss';
10
- @source "../../src/**/*.{ts,tsx}";
10
+ @source '../../src/**/*.{ts,tsx}';
11
11
  @import './theme.css';