@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
@@ -17,10 +17,10 @@ export function RowRenderer({ node, selectedNodeId, onSelectNode }) {
17
17
  e.stopPropagation();
18
18
  onSelectNode(node.id);
19
19
  };
20
- return (_jsxs("div", { "data-node-id": node.id, className: `relative transition-shadow ${isSelected ? 'ring-2 ring-primary ring-offset-2' : hovered ? 'ring-1 ring-primary/50' : ''}`, onClick: handleClick, onMouseEnter: (e) => {
20
+ return (_jsxs("div", { "data-node-id": node.id, className: `relative transition-shadow ${isSelected ? 'ring-primary ring-2 ring-offset-2' : hovered ? 'ring-primary/50 ring-1' : ''}`, onClick: handleClick, onMouseEnter: (e) => {
21
21
  e.stopPropagation();
22
22
  setHovered(true);
23
- }, onMouseLeave: () => setHovered(false), children: [(hovered || isSelected) && (_jsx("span", { className: "absolute -top-2 -left-1 text-xs px-1.5 py-0.5 bg-primary text-primary-foreground rounded font-medium z-10", children: "Row" })), _jsx("div", { className: "grid grid-cols-12", style: {
23
+ }, onMouseLeave: () => setHovered(false), children: [(hovered || isSelected) && (_jsx("span", { className: "bg-primary text-primary-foreground absolute -top-2 -left-1 z-10 rounded px-1.5 py-0.5 text-xs font-medium", children: "Row" })), _jsx("div", { className: "grid grid-cols-12", style: {
24
24
  gap,
25
25
  alignItems: alignMap[verticalAlign] ?? 'stretch',
26
26
  }, children: node.children.map((col) => (_jsx(ColumnRenderer, { node: col, selectedNodeId: selectedNodeId, onSelectNode: onSelectNode }, col.id))) })] }));
@@ -34,10 +34,10 @@ export function SectionRenderer({ node, selectedNodeId, onSelectNode }) {
34
34
  if (node.settings.marginBottom) {
35
35
  backgroundStyle.marginBottom = node.settings.marginBottom;
36
36
  }
37
- return (_jsxs("div", { "data-node-id": node.id, className: `relative w-full transition-shadow ${isSelected ? 'ring-2 ring-primary ring-offset-2' : hovered ? 'ring-1 ring-primary/50' : ''}`, style: backgroundStyle, onClick: handleClick, onMouseEnter: (e) => {
37
+ return (_jsxs("div", { "data-node-id": node.id, className: `relative w-full transition-shadow ${isSelected ? 'ring-primary ring-2 ring-offset-2' : hovered ? 'ring-primary/50 ring-1' : ''}`, style: backgroundStyle, onClick: handleClick, onMouseEnter: (e) => {
38
38
  e.stopPropagation();
39
39
  setHovered(true);
40
- }, onMouseLeave: () => setHovered(false), children: [(hovered || isSelected) && (_jsx("span", { className: "absolute -top-2 -left-1 text-xs px-1.5 py-0.5 bg-primary text-primary-foreground rounded font-medium z-10", children: "Section" })), node.children.map((child) => {
40
+ }, onMouseLeave: () => setHovered(false), children: [(hovered || isSelected) && (_jsx("span", { className: "bg-primary text-primary-foreground absolute -top-2 -left-1 z-10 rounded px-1.5 py-0.5 text-xs font-medium", children: "Section" })), node.children.map((child) => {
41
41
  if (child.type === 'container') {
42
42
  return (_jsx(ContainerRenderer, { node: child, selectedNodeId: selectedNodeId, onSelectNode: onSelectNode }, child.id));
43
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-admin",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/actuate-media/actuatecms.git",
@@ -68,14 +68,18 @@
68
68
  "@tailwindcss/cli": "^4.0.0",
69
69
  "@types/react": "^19.0.0",
70
70
  "@types/react-dom": "^19.0.0",
71
+ "concurrently": "^9.2.1",
71
72
  "tailwindcss": "^4.0.0",
72
73
  "typescript": "^5.7.0",
73
74
  "vitest": "^3.0.0",
74
- "@actuate-media/cms-core": "0.14.0"
75
+ "@actuate-media/cms-core": "0.20.0"
75
76
  },
76
77
  "scripts": {
77
78
  "build": "tsc --project tsconfig.json && npx @tailwindcss/cli -i src/styles/build-input.css -o dist/actuate-admin.css --minify",
78
79
  "build:css": "npx @tailwindcss/cli -i src/styles/build-input.css -o dist/actuate-admin.css --minify",
80
+ "dev": "concurrently -n tsc,css -c blue,magenta \"pnpm run dev:tsc\" \"pnpm run dev:css\"",
81
+ "dev:tsc": "tsc --project tsconfig.json --watch --preserveWatchOutput",
82
+ "dev:css": "npx @tailwindcss/cli -i src/styles/build-input.css -o dist/actuate-admin.css --watch",
79
83
  "type-check": "tsc --noEmit",
80
84
  "test": "vitest run",
81
85
  "clean": "rm -rf dist .turbo"
package/src/AdminRoot.tsx CHANGED
@@ -143,13 +143,23 @@ function AdminShell({
143
143
  }
144
144
 
145
145
  if (onLogin) {
146
- return <Login onLogin={onLogin} onNavigate={navigate} captchaConfig={captchaConfig} />
146
+ // Thread `config.admin.branding` through so the login screen matches the
147
+ // sidebar (single source of truth for the logo + brand name).
148
+ const branding = config?.admin?.branding
149
+ return (
150
+ <Login
151
+ onLogin={onLogin}
152
+ onNavigate={navigate}
153
+ captchaConfig={captchaConfig}
154
+ branding={branding}
155
+ />
156
+ )
147
157
  }
148
158
  return (
149
- <div className="min-h-screen flex items-center justify-center bg-background">
159
+ <div className="bg-background flex min-h-screen items-center justify-center">
150
160
  <div className="text-center">
151
- <h1 className="text-xl font-semibold text-foreground mb-2">Unauthorized</h1>
152
- <p className="text-sm text-muted-foreground">Please log in to access the admin panel.</p>
161
+ <h1 className="text-foreground mb-2 text-xl font-semibold">Unauthorized</h1>
162
+ <p className="text-muted-foreground text-sm">Please log in to access the admin panel.</p>
153
163
  </div>
154
164
  </div>
155
165
  )
@@ -331,12 +341,12 @@ function AdminShell({
331
341
  }
332
342
 
333
343
  return (
334
- <div className="flex flex-col items-center justify-center min-h-[400px] text-center p-6">
335
- <h1 className="text-4xl font-bold text-foreground mb-2">404</h1>
344
+ <div className="flex min-h-[400px] flex-col items-center justify-center p-6 text-center">
345
+ <h1 className="text-foreground mb-2 text-4xl font-bold">404</h1>
336
346
  <p className="text-muted-foreground mb-4">The page you are looking for does not exist.</p>
337
347
  <button
338
348
  onClick={() => navigate('/')}
339
- className="px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-md hover:opacity-90"
349
+ className="text-primary-foreground bg-primary rounded-md px-4 py-2 text-sm font-medium hover:opacity-90"
340
350
  >
341
351
  Back to Dashboard
342
352
  </button>
@@ -360,8 +370,8 @@ function ShortcutHelp({ onClose }: { onClose: () => void }) {
360
370
  if (e.target === e.currentTarget) onClose()
361
371
  }}
362
372
  >
363
- <div className="bg-card text-card-foreground rounded-xl shadow-2xl border border-border p-6 max-w-sm w-full mx-4">
364
- <h3 className="text-lg font-semibold mb-4">Keyboard Shortcuts</h3>
373
+ <div className="bg-card text-card-foreground border-border mx-4 w-full max-w-sm rounded-xl border p-6 shadow-2xl">
374
+ <h3 className="mb-4 text-lg font-semibold">Keyboard Shortcuts</h3>
365
375
  <div className="space-y-3 text-sm">
366
376
  {[
367
377
  ['⌘ K', 'Open search'],
@@ -371,13 +381,13 @@ function ShortcutHelp({ onClose }: { onClose: () => void }) {
371
381
  ].map(([key, desc]) => (
372
382
  <div key={key} className="flex items-center justify-between">
373
383
  <span className="text-muted-foreground">{desc}</span>
374
- <kbd className="px-2 py-1 text-xs font-mono bg-muted rounded">{key}</kbd>
384
+ <kbd className="bg-muted rounded px-2 py-1 font-mono text-xs">{key}</kbd>
375
385
  </div>
376
386
  ))}
377
387
  </div>
378
388
  <button
379
389
  onClick={onClose}
380
- className="mt-4 w-full py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90"
390
+ className="bg-primary text-primary-foreground mt-4 w-full rounded-lg py-2 text-sm hover:opacity-90"
381
391
  >
382
392
  Close
383
393
  </button>
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { tokens } from '../../layout/primitives/tokens.js'
3
+ import * as primitives from '../../layout/primitives/index.js'
4
+
5
+ describe('layout primitive tokens', () => {
6
+ it('exposes the canonical spacing scale', () => {
7
+ expect(tokens.space['0']).toBe(0)
8
+ expect(tokens.space['1']).toBe(4)
9
+ expect(tokens.space['4']).toBe(16)
10
+ expect(tokens.space['8']).toBe(32)
11
+ })
12
+
13
+ it('exposes the canonical radius scale', () => {
14
+ expect(tokens.radius.none).toBe(0)
15
+ expect(tokens.radius.md).toBe(6)
16
+ expect(tokens.radius.full).toBe(9999)
17
+ })
18
+
19
+ it('exposes the canonical breakpoint scale', () => {
20
+ expect(tokens.breakpoint.sm).toBe(640)
21
+ expect(tokens.breakpoint.md).toBe(768)
22
+ expect(tokens.breakpoint.lg).toBe(1024)
23
+ })
24
+ })
25
+
26
+ describe('layout primitives exports', () => {
27
+ it('re-exports every public primitive', () => {
28
+ expect(typeof primitives.AdminShell).toBe('function')
29
+ expect(typeof primitives.PageContainer).toBe('function')
30
+ expect(typeof primitives.Stack).toBe('function')
31
+ expect(typeof primitives.Cluster).toBe('function')
32
+ expect(typeof primitives.Grid).toBe('function')
33
+ expect(typeof primitives.Split).toBe('function')
34
+ expect(typeof primitives.Box).toBe('object')
35
+ expect(typeof primitives.tokens).toBe('object')
36
+ })
37
+ })
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { cv } from '../../lib/cv.js'
3
+
4
+ describe('cv()', () => {
5
+ const button = cv('inline-flex items-center', {
6
+ variants: {
7
+ intent: {
8
+ primary: 'bg-primary text-white',
9
+ secondary: 'bg-secondary text-foreground',
10
+ },
11
+ size: {
12
+ sm: 'px-2 py-1 text-xs',
13
+ md: 'px-4 py-2 text-sm',
14
+ lg: 'px-6 py-3 text-base',
15
+ },
16
+ },
17
+ defaultVariants: { intent: 'primary', size: 'md' },
18
+ })
19
+
20
+ it('returns the base class and default variant classes when no props supplied', () => {
21
+ const out = button()
22
+ expect(out).toContain('inline-flex')
23
+ expect(out).toContain('bg-primary')
24
+ expect(out).toContain('text-white')
25
+ expect(out).toContain('px-4 py-2 text-sm')
26
+ })
27
+
28
+ it('overrides default variants when explicit values are supplied', () => {
29
+ const out = button({ intent: 'secondary', size: 'sm' })
30
+ expect(out).toContain('bg-secondary')
31
+ expect(out).toContain('text-foreground')
32
+ expect(out).toContain('px-2 py-1 text-xs')
33
+ expect(out).not.toContain('bg-primary')
34
+ })
35
+
36
+ it('appends the `class` escape hatch', () => {
37
+ const out = button({ class: 'shadow-md' })
38
+ expect(out).toContain('shadow-md')
39
+ })
40
+
41
+ it('appends the `className` escape hatch', () => {
42
+ const out = button({ className: 'rounded-full' })
43
+ expect(out).toContain('rounded-full')
44
+ })
45
+
46
+ it('applies compound variants when their match criteria are satisfied', () => {
47
+ const card = cv('rounded-md', {
48
+ variants: {
49
+ tone: { neutral: 'bg-gray-100', danger: 'bg-red-100' },
50
+ emphasis: { low: 'border', high: 'border-2' },
51
+ },
52
+ defaultVariants: { tone: 'neutral', emphasis: 'low' },
53
+ compoundVariants: [{ tone: 'danger', emphasis: 'high', class: 'ring-2 ring-red-500' }],
54
+ })
55
+
56
+ expect(card({ tone: 'danger', emphasis: 'high' })).toContain('ring-2 ring-red-500')
57
+ expect(card({ tone: 'danger', emphasis: 'low' })).not.toContain('ring-2')
58
+ expect(card({ tone: 'neutral', emphasis: 'high' })).not.toContain('ring-2')
59
+ })
60
+
61
+ it('applies compound variants with array (any-of) matching', () => {
62
+ const alert = cv('alert', {
63
+ variants: {
64
+ tone: { info: 'bg-blue-50', warn: 'bg-amber-50', danger: 'bg-red-50' },
65
+ },
66
+ defaultVariants: { tone: 'info' },
67
+ compoundVariants: [{ tone: ['warn', 'danger'], class: 'font-medium' }],
68
+ })
69
+
70
+ expect(alert({ tone: 'warn' })).toContain('font-medium')
71
+ expect(alert({ tone: 'danger' })).toContain('font-medium')
72
+ expect(alert({ tone: 'info' })).not.toContain('font-medium')
73
+ })
74
+ })
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Default Actuate Media brand logo for the admin sidebar (top-left) and the
3
+ * login screen.
4
+ *
5
+ * Source: the official 2021 Actuate Media SVG. The original asset has a
6
+ * 1000×800 viewBox with a white background polygon and the artwork
7
+ * concentrated in the middle band — we strip the white polygon so the logo
8
+ * is transparent on any surface, and crop the viewBox to the actual content
9
+ * bounds so `h-9 w-auto` renders at a sensible aspect ratio instead of
10
+ * leaving 60% empty space.
11
+ *
12
+ * Integrators override this default via:
13
+ * defineConfig({
14
+ * admin: { branding: { logo: '/my-logo.svg', name: 'Acme' } }
15
+ * })
16
+ *
17
+ * Both the sidebar (top-left) and the login screen read from the same
18
+ * `admin.branding` config so swapping logos is a one-line change.
19
+ */
20
+
21
+ import type { ReactElement } from 'react'
22
+
23
+ export interface BrandLogoProps {
24
+ /** Tailwind / CSS class applied to the root `<svg>` element. */
25
+ className?: string
26
+ }
27
+
28
+ /**
29
+ * Inline SVG renderer for the official Actuate Media lockup. Transparent
30
+ * background, scales cleanly at any size, and ships as JSX (no bundler asset
31
+ * pipeline required — works under Next.js, Vite, Webpack, esbuild, etc.).
32
+ *
33
+ * Colour palette is baked into the path fills so the logo stays on-brand
34
+ * regardless of the surrounding theme:
35
+ * - Coral / red `#F05E65` for the "C" mark + ACTUATE wordmark
36
+ * - Neutral grey `#818386` for the MEDIA sub-line
37
+ */
38
+ export function ActuateBrandLogo({ className }: BrandLogoProps): ReactElement {
39
+ return (
40
+ <svg
41
+ viewBox="160 250 730 290"
42
+ xmlns="http://www.w3.org/2000/svg"
43
+ className={className}
44
+ role="img"
45
+ aria-label="Actuate Media"
46
+ style={{ shapeRendering: 'geometricPrecision' }}
47
+ >
48
+ {/* "C" mark: upward arrow + three vertical bars (coral) */}
49
+ <polygon
50
+ fill="#F05E65"
51
+ fillRule="nonzero"
52
+ points="253,266 167,433 196,433 253,323 309,433 339,433"
53
+ />
54
+ <path
55
+ fill="#F05E65"
56
+ d="M216 433l16 0 0 59 -16 0 0 -59zm57 0l17 0 0 59 -17 0 0 -59zm-28 0l16 0 0 101 -16 0 0 -101z"
57
+ />
58
+ {/* "MEDIA" subtitle (neutral grey) */}
59
+ <path
60
+ fill="#818386"
61
+ fillRule="nonzero"
62
+ d="M707 447l-12 0 6 -14 6 14zm-241 -11l0 25 6 0 0 -35 -7 0 -11 17 -10 -17 -7 0 0 35 6 0 0 -25 11 17 1 0 11 -17zm70 -10l-26 0 0 35 26 0 0 -5 -20 0 0 -10 20 0 0 -5 -20 0 0 -10 20 0 0 -5zm62 18c0,1 0,3 -1,5 0,1 -1,2 -2,3 -1,1 -3,2 -4,3 -2,1 -3,1 -5,1l-7 0 0 -25 7 0c2,0 3,1 5,1 1,1 3,2 4,3 1,1 2,2 2,4 1,1 1,3 1,5zm7 0c0,-3 -1,-5 -2,-7 -1,-3 -2,-5 -4,-6 -1,-2 -3,-3 -6,-4 -2,-1 -5,-1 -7,-1l-14 0 0 35 14 0c2,0 5,0 7,-1 3,-1 5,-2 6,-4 2,-1 3,-3 4,-5 1,-3 2,-5 2,-7zm36 -18l0 35 6 0 0 -35 -6 0zm63 0l-6 0 -16 35 7 0 3 -8 17 0 4 8 7 0 -16 -35z"
63
+ />
64
+ {/* "ACTUATE" main wordmark (coral) */}
65
+ <path
66
+ fill="#F05E65"
67
+ fillRule="nonzero"
68
+ d="M406 386l-10 -10c-4,3 -7,6 -11,8 -4,2 -8,3 -13,3 -5,0 -8,-1 -12,-2 -3,-2 -6,-4 -9,-7 -2,-3 -4,-6 -6,-10 -1,-3 -2,-7 -2,-12 0,-4 1,-8 2,-11 2,-4 4,-7 6,-10 3,-3 6,-5 9,-7 4,-1 7,-2 12,-2 4,0 9,1 13,3 3,2 7,5 11,8l9 -11c-2,-2 -4,-4 -6,-6 -3,-1 -5,-3 -8,-4 -2,-1 -5,-2 -9,-3 -3,0 -6,-1 -10,-1 -7,0 -13,1 -18,4 -6,2 -10,5 -14,9 -4,4 -7,9 -9,14 -2,6 -4,12 -4,18 0,6 2,12 4,17 2,5 5,10 9,14 4,4 8,7 14,9 5,3 11,4 17,4 4,0 8,0 11,-1 3,-1 6,-2 9,-3 3,-2 5,-3 8,-5 2,-2 5,-4 7,-6zm230 -73l-45 86 16 0 29 -57 29 57 15 0 -44 -86zm197 1l-63 0 0 85 63 0 0 -13 -48 0 0 -23 48 0 0 -14 -48 0 0 -22 48 0 0 -13zm-110 14l27 0 0 -14 -70 0 0 14 28 0 0 71 15 0 0 -71zm-140 -14l-15 0 0 49c0,8 -2,14 -6,18 -4,4 -9,6 -16,6 -7,0 -12,-2 -16,-6 -4,-4 -6,-11 -6,-19l0 -48 -15 0 0 49c0,6 1,12 3,16 2,5 4,9 7,12 3,3 7,6 12,7 4,2 9,3 15,3 5,0 11,-1 15,-3 5,-1 8,-4 12,-7 3,-3 5,-7 7,-12 2,-5 3,-10 3,-17l0 -48zm-118 14l27 0 0 -14 -69 0 0 14 27 0 0 71 15 0 0 -71z"
69
+ />
70
+ </svg>
71
+ )
72
+ }
@@ -61,29 +61,29 @@ export function Breadcrumbs({ currentPath, onNavigate }: BreadcrumbsProps) {
61
61
  return (
62
62
  <nav
63
63
  aria-label="Breadcrumb"
64
- className="flex items-center gap-1 text-sm py-2.5 px-4 bg-white border-b border-gray-200 overflow-x-auto"
64
+ className="flex items-center gap-1 overflow-x-auto border-b border-gray-200 bg-white px-4 py-2.5 text-sm"
65
65
  >
66
66
  <button
67
67
  type="button"
68
68
  onClick={() => onNavigate('/')}
69
- className="flex items-center gap-1 text-gray-500 hover:text-blue-600 transition-colors shrink-0"
69
+ className="flex shrink-0 items-center gap-1 text-gray-500 transition-colors hover:text-blue-600"
70
70
  >
71
- <Home className="w-3.5 h-3.5" />
71
+ <Home className="h-3.5 w-3.5" />
72
72
  <span className="hidden sm:inline">Dashboard</span>
73
73
  </button>
74
74
 
75
75
  {crumbs.map((crumb, i) => {
76
76
  const isLast = i === crumbs.length - 1
77
77
  return (
78
- <span key={crumb.path} className="flex items-center gap-1 shrink-0">
79
- <ChevronRight className="w-3.5 h-3.5 text-gray-400" />
78
+ <span key={crumb.path} className="flex shrink-0 items-center gap-1">
79
+ <ChevronRight className="h-3.5 w-3.5 text-gray-400" />
80
80
  {isLast ? (
81
81
  <span className="font-medium text-gray-900">{crumb.label}</span>
82
82
  ) : (
83
83
  <button
84
84
  type="button"
85
85
  onClick={() => onNavigate(crumb.path)}
86
- className="text-gray-500 hover:text-blue-600 transition-colors"
86
+ className="text-gray-500 transition-colors hover:text-blue-600"
87
87
  >
88
88
  {crumb.label}
89
89
  </button>
@@ -181,7 +181,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
181
181
 
182
182
  return (
183
183
  <div
184
- className="fixed inset-0 z-50 bg-black/50 flex items-start justify-center pt-[20vh] px-4"
184
+ className="fixed inset-0 z-50 flex items-start justify-center bg-black/50 px-4 pt-[20vh]"
185
185
  onClick={() => {
186
186
  onOpenChange(false)
187
187
  setSearch('')
@@ -195,7 +195,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
195
195
  onClick={(e) => e.stopPropagation()}
196
196
  >
197
197
  <Command
198
- className="bg-white rounded-lg shadow-2xl w-full overflow-hidden"
198
+ className="w-full overflow-hidden rounded-lg bg-white shadow-2xl"
199
199
  onKeyDown={(e) => {
200
200
  if (e.key === 'Escape') {
201
201
  onOpenChange(false)
@@ -205,17 +205,17 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
205
205
  shouldFilter={!hasQuery}
206
206
  >
207
207
  <div className="flex items-center border-b border-gray-200 px-4">
208
- <Search className="w-5 h-5 text-gray-400 mr-3 shrink-0" />
208
+ <Search className="mr-3 h-5 w-5 shrink-0 text-gray-400" />
209
209
  <Command.Input
210
210
  value={search}
211
211
  onValueChange={setSearch}
212
212
  placeholder="Search or jump to..."
213
- className="w-full py-4 text-base bg-transparent focus:outline-none placeholder:text-gray-400"
213
+ className="w-full bg-transparent py-4 text-base placeholder:text-gray-400 focus:outline-none"
214
214
  />
215
215
  {loading && (
216
- <div className="w-4 h-4 border-2 border-gray-300 border-t-gray-600 rounded-full animate-spin shrink-0 mr-2" />
216
+ <div className="mr-2 h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600" />
217
217
  )}
218
- <kbd className="hidden sm:inline-block px-2 py-1 text-xs font-mono bg-gray-100 text-gray-600 rounded shrink-0">
218
+ <kbd className="hidden shrink-0 rounded bg-gray-100 px-2 py-1 font-mono text-xs text-gray-600 sm:inline-block">
219
219
  ESC
220
220
  </kbd>
221
221
  </div>
@@ -227,15 +227,15 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
227
227
 
228
228
  {!hasQuery && recentItems.length > 0 && (
229
229
  <Command.Group heading="Recent" className="px-2 py-2">
230
- <div className="text-xs font-medium text-gray-500 mb-2">RECENT</div>
230
+ <div className="mb-2 text-xs font-medium text-gray-500">RECENT</div>
231
231
  {recentItems.map((item) => (
232
232
  <Command.Item
233
233
  key={`recent-${item.id}`}
234
234
  value={`recent ${item.label}`}
235
235
  onSelect={() => handleSelect(item.path, item)}
236
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
236
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
237
237
  >
238
- <Clock className="w-4 h-4 text-gray-400" />
238
+ <Clock className="h-4 w-4 text-gray-400" />
239
239
  <span>{item.label}</span>
240
240
  <span className="ml-auto text-xs text-gray-400 capitalize">{item.type}</span>
241
241
  </Command.Item>
@@ -245,7 +245,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
245
245
 
246
246
  {hasQuery && documents.length > 0 && (
247
247
  <Command.Group heading="Documents" className="px-2 py-2">
248
- <div className="text-xs font-medium text-gray-500 mb-2">DOCUMENTS</div>
248
+ <div className="mb-2 text-xs font-medium text-gray-500">DOCUMENTS</div>
249
249
  {documents.map((doc) => (
250
250
  <Command.Item
251
251
  key={`doc-${doc.id}`}
@@ -258,10 +258,10 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
258
258
  type: 'document',
259
259
  })
260
260
  }
261
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
261
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
262
262
  >
263
- <FileText className="w-4 h-4 text-gray-500" />
264
- <div className="flex flex-col min-w-0">
263
+ <FileText className="h-4 w-4 text-gray-500" />
264
+ <div className="flex min-w-0 flex-col">
265
265
  <span className="truncate">{doc.title ?? doc.slug ?? doc.id}</span>
266
266
  <span className="text-xs text-gray-400">
267
267
  {doc.collection} &middot; {doc.status}
@@ -273,8 +273,8 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
273
273
  )}
274
274
 
275
275
  {hasQuery && media.length > 0 && (
276
- <Command.Group heading="Media" className="px-2 py-2 mt-1">
277
- <div className="text-xs font-medium text-gray-500 mb-2">MEDIA</div>
276
+ <Command.Group heading="Media" className="mt-1 px-2 py-2">
277
+ <div className="mb-2 text-xs font-medium text-gray-500">MEDIA</div>
278
278
  {media.map((m) => (
279
279
  <Command.Item
280
280
  key={`media-${m.id}`}
@@ -287,10 +287,10 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
287
287
  type: 'media',
288
288
  })
289
289
  }
290
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
290
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
291
291
  >
292
- <Image className="w-4 h-4 text-gray-500" />
293
- <div className="flex flex-col min-w-0">
292
+ <Image className="h-4 w-4 text-gray-500" />
293
+ <div className="flex min-w-0 flex-col">
294
294
  <span className="truncate">{m.filename}</span>
295
295
  <span className="text-xs text-gray-400">{m.mimeType}</span>
296
296
  </div>
@@ -300,8 +300,8 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
300
300
  )}
301
301
 
302
302
  {hasQuery && users.length > 0 && (
303
- <Command.Group heading="Users" className="px-2 py-2 mt-1">
304
- <div className="text-xs font-medium text-gray-500 mb-2">USERS</div>
303
+ <Command.Group heading="Users" className="mt-1 px-2 py-2">
304
+ <div className="mb-2 text-xs font-medium text-gray-500">USERS</div>
305
305
  {users.map((u) => (
306
306
  <Command.Item
307
307
  key={`user-${u.id}`}
@@ -314,10 +314,10 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
314
314
  type: 'user',
315
315
  })
316
316
  }
317
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
317
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
318
318
  >
319
- <Users className="w-4 h-4 text-gray-500" />
320
- <div className="flex flex-col min-w-0">
319
+ <Users className="h-4 w-4 text-gray-500" />
320
+ <div className="flex min-w-0 flex-col">
321
321
  <span className="truncate">{u.name}</span>
322
322
  <span className="text-xs text-gray-400">
323
323
  {u.email} &middot; {u.role}
@@ -330,7 +330,7 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
330
330
 
331
331
  {(!hasQuery || !hasResults) && (
332
332
  <Command.Group heading="Navigation" className="px-2 py-2">
333
- <div className="text-xs font-medium text-gray-500 mb-2">NAVIGATION</div>
333
+ <div className="mb-2 text-xs font-medium text-gray-500">NAVIGATION</div>
334
334
  {navigationCommands.map((cmd) => {
335
335
  const Icon = cmd.icon
336
336
  return (
@@ -338,9 +338,9 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
338
338
  key={cmd.id}
339
339
  value={cmd.label}
340
340
  onSelect={() => handleSelect(cmd.action)}
341
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
341
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
342
342
  >
343
- <Icon className="w-4 h-4 text-gray-500" />
343
+ <Icon className="h-4 w-4 text-gray-500" />
344
344
  <span>{cmd.label}</span>
345
345
  </Command.Item>
346
346
  )
@@ -348,8 +348,8 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
348
348
  </Command.Group>
349
349
  )}
350
350
 
351
- <Command.Group heading="Actions" className="px-2 py-2 mt-2">
352
- <div className="text-xs font-medium text-gray-500 mb-2">ACTIONS</div>
351
+ <Command.Group heading="Actions" className="mt-2 px-2 py-2">
352
+ <div className="mb-2 text-xs font-medium text-gray-500">ACTIONS</div>
353
353
  {staticActions.map((cmd) => {
354
354
  const Icon = cmd.icon
355
355
  return (
@@ -364,9 +364,9 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
364
364
  type: 'action',
365
365
  })
366
366
  }
367
- className="flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer hover:bg-gray-100 data-[selected=true]:bg-gray-100"
367
+ className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 hover:bg-gray-100 data-[selected=true]:bg-gray-100"
368
368
  >
369
- <Icon className="w-4 h-4 text-gray-500" />
369
+ <Icon className="h-4 w-4 text-gray-500" />
370
370
  <span>{cmd.label}</span>
371
371
  </Command.Item>
372
372
  )
@@ -374,19 +374,19 @@ export function CommandPalette({ open, onOpenChange, onNavigate }: CommandPalett
374
374
  </Command.Group>
375
375
  </Command.List>
376
376
 
377
- <div className="border-t border-gray-200 px-4 py-3 bg-gray-50 flex items-center justify-between text-xs text-gray-500">
377
+ <div className="flex items-center justify-between border-t border-gray-200 bg-gray-50 px-4 py-3 text-xs text-gray-500">
378
378
  <div className="flex items-center gap-4">
379
379
  <span className="flex items-center gap-1">
380
- <kbd className="px-1.5 py-0.5 bg-white border border-gray-300 rounded text-[10px]">
380
+ <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
381
381
 
382
382
  </kbd>
383
- <kbd className="px-1.5 py-0.5 bg-white border border-gray-300 rounded text-[10px]">
383
+ <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
384
384
 
385
385
  </kbd>
386
386
  to navigate
387
387
  </span>
388
388
  <span className="flex items-center gap-1">
389
- <kbd className="px-1.5 py-0.5 bg-white border border-gray-300 rounded text-[10px]">
389
+ <kbd className="rounded border border-gray-300 bg-white px-1.5 py-0.5 text-[10px]">
390
390
 
391
391
  </kbd>
392
392
  to select
@@ -20,7 +20,7 @@ export function ContentOverviewChart({ published, drafts, scheduled }: ContentOv
20
20
 
21
21
  if (total === 0) {
22
22
  return (
23
- <div className="flex items-center justify-center h-full text-sm text-gray-400">
23
+ <div className="flex h-full items-center justify-center text-sm text-gray-400">
24
24
  No content yet
25
25
  </div>
26
26
  )
@@ -28,7 +28,7 @@ export function ContentOverviewChart({ published, drafts, scheduled }: ContentOv
28
28
 
29
29
  return (
30
30
  <div className="flex items-center gap-6">
31
- <div className="w-36 h-36 shrink-0">
31
+ <div className="h-36 w-36 shrink-0">
32
32
  <ResponsiveContainer width="100%" height="100%">
33
33
  <PieChart>
34
34
  <Pie
@@ -54,7 +54,7 @@ export function ContentOverviewChart({ published, drafts, scheduled }: ContentOv
54
54
  return (
55
55
  <div key={entry.name} className="flex items-center gap-3">
56
56
  <span
57
- className="w-2.5 h-2.5 rounded-full shrink-0"
57
+ className="h-2.5 w-2.5 shrink-0 rounded-full"
58
58
  style={{ backgroundColor: COLORS[idx] }}
59
59
  />
60
60
  <div className="min-w-0">
@@ -32,13 +32,13 @@ export class ErrorBoundary extends Component<Props, State> {
32
32
  return (
33
33
  <div className="flex min-h-[200px] items-center justify-center p-6">
34
34
  <div className="text-center">
35
- <h2 className="text-lg font-medium text-foreground mb-2">Something went wrong</h2>
36
- <p className="text-sm text-muted-foreground mb-4">
35
+ <h2 className="text-foreground mb-2 text-lg font-medium">Something went wrong</h2>
36
+ <p className="text-muted-foreground mb-4 text-sm">
37
37
  {this.state.error?.message ?? 'An unexpected error occurred'}
38
38
  </p>
39
39
  <button
40
40
  onClick={() => this.setState({ hasError: false, error: null })}
41
- className="px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-md hover:bg-primary/90 transition-colors"
41
+ className="text-primary-foreground bg-primary hover:bg-primary/90 rounded-md px-4 py-2 text-sm font-medium transition-colors"
42
42
  >
43
43
  Try Again
44
44
  </button>
@@ -28,12 +28,12 @@ export function FocalPointPicker({ imageUrl, focalX, focalY, onChange }: FocalPo
28
28
  return (
29
29
  <div className="space-y-2">
30
30
  <div className="flex items-center gap-2 text-sm font-medium text-[var(--foreground)]">
31
- <Crosshair className="w-4 h-4" />
31
+ <Crosshair className="h-4 w-4" />
32
32
  Focal Point
33
33
  </div>
34
34
  <div
35
35
  ref={containerRef}
36
- className="relative rounded-lg overflow-hidden border border-[var(--border)] cursor-crosshair select-none"
36
+ className="relative cursor-crosshair overflow-hidden rounded-lg border border-[var(--border)] select-none"
37
37
  style={{ maxHeight: '200px' }}
38
38
  onClick={handlePosition}
39
39
  onMouseDown={() => setDragging(true)}
@@ -46,11 +46,11 @@ export function FocalPointPicker({ imageUrl, focalX, focalY, onChange }: FocalPo
46
46
  <img
47
47
  src={imageUrl}
48
48
  alt="Focal point preview"
49
- className="w-full h-full object-contain"
49
+ className="h-full w-full object-contain"
50
50
  draggable={false}
51
51
  />
52
52
  <div
53
- className="absolute w-6 h-6 -ml-3 -mt-3 rounded-full border-2 border-white shadow-lg bg-blue-500/50 pointer-events-none"
53
+ className="pointer-events-none absolute -mt-3 -ml-3 h-6 w-6 rounded-full border-2 border-white bg-blue-500/50 shadow-lg"
54
54
  style={{ left: `${focalX * 100}%`, top: `${focalY * 100}%` }}
55
55
  >
56
56
  <div className="absolute inset-1 rounded-full bg-white" />