@actuate-media/cms-admin 0.10.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 (284) 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.js +1 -1
  34. package/dist/components/SharePreviewLinkDialog.js +1 -1
  35. package/dist/components/TipTapEditor.js +5 -5
  36. package/dist/components/VersionHistory.js +2 -2
  37. package/dist/components/ui/Badge.d.ts +33 -3
  38. package/dist/components/ui/Badge.d.ts.map +1 -1
  39. package/dist/components/ui/Badge.js +42 -8
  40. package/dist/components/ui/Badge.js.map +1 -1
  41. package/dist/components/ui/Button.d.ts +19 -8
  42. package/dist/components/ui/Button.d.ts.map +1 -1
  43. package/dist/components/ui/Button.js +35 -14
  44. package/dist/components/ui/Button.js.map +1 -1
  45. package/dist/components/ui/Card.d.ts +26 -0
  46. package/dist/components/ui/Card.d.ts.map +1 -0
  47. package/dist/components/ui/Card.js +45 -0
  48. package/dist/components/ui/Card.js.map +1 -0
  49. package/dist/components/ui/DataTable.js +1 -1
  50. package/dist/components/ui/Input.d.ts +15 -0
  51. package/dist/components/ui/Input.d.ts.map +1 -0
  52. package/dist/components/ui/Input.js +23 -0
  53. package/dist/components/ui/Input.js.map +1 -0
  54. package/dist/components/ui/SearchInput.js +1 -1
  55. package/dist/components/ui/Select.d.ts +16 -0
  56. package/dist/components/ui/Select.d.ts.map +1 -0
  57. package/dist/components/ui/Select.js +25 -0
  58. package/dist/components/ui/Select.js.map +1 -0
  59. package/dist/components/ui/Toast.js +1 -1
  60. package/dist/components/ui/index.d.ts +10 -4
  61. package/dist/components/ui/index.d.ts.map +1 -1
  62. package/dist/components/ui/index.js +5 -2
  63. package/dist/components/ui/index.js.map +1 -1
  64. package/dist/fields/BlockBuilderField.js +3 -3
  65. package/dist/fields/DateField.js +1 -1
  66. package/dist/fields/RelationshipField.js +3 -3
  67. package/dist/fields/TextField.js +1 -1
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +3 -0
  71. package/dist/index.js.map +1 -1
  72. package/dist/layout/Header.js +1 -1
  73. package/dist/layout/Layout.d.ts +14 -0
  74. package/dist/layout/Layout.d.ts.map +1 -1
  75. package/dist/layout/Layout.js +17 -11
  76. package/dist/layout/Layout.js.map +1 -1
  77. package/dist/layout/Sidebar.d.ts.map +1 -1
  78. package/dist/layout/Sidebar.js +21 -11
  79. package/dist/layout/Sidebar.js.map +1 -1
  80. package/dist/layout/primitives/AdminShell.d.ts +43 -0
  81. package/dist/layout/primitives/AdminShell.d.ts.map +1 -0
  82. package/dist/layout/primitives/AdminShell.js +51 -0
  83. package/dist/layout/primitives/AdminShell.js.map +1 -0
  84. package/dist/layout/primitives/Box.d.ts +19 -0
  85. package/dist/layout/primitives/Box.d.ts.map +1 -0
  86. package/dist/layout/primitives/Box.js +12 -0
  87. package/dist/layout/primitives/Box.js.map +1 -0
  88. package/dist/layout/primitives/Cluster.d.ts +27 -0
  89. package/dist/layout/primitives/Cluster.d.ts.map +1 -0
  90. package/dist/layout/primitives/Cluster.js +37 -0
  91. package/dist/layout/primitives/Cluster.js.map +1 -0
  92. package/dist/layout/primitives/Grid.d.ts +45 -0
  93. package/dist/layout/primitives/Grid.d.ts.map +1 -0
  94. package/dist/layout/primitives/Grid.js +59 -0
  95. package/dist/layout/primitives/Grid.js.map +1 -0
  96. package/dist/layout/primitives/PageContainer.d.ts +36 -0
  97. package/dist/layout/primitives/PageContainer.d.ts.map +1 -0
  98. package/dist/layout/primitives/PageContainer.js +41 -0
  99. package/dist/layout/primitives/PageContainer.js.map +1 -0
  100. package/dist/layout/primitives/Split.d.ts +34 -0
  101. package/dist/layout/primitives/Split.d.ts.map +1 -0
  102. package/dist/layout/primitives/Split.js +27 -0
  103. package/dist/layout/primitives/Split.js.map +1 -0
  104. package/dist/layout/primitives/Stack.d.ts +23 -0
  105. package/dist/layout/primitives/Stack.d.ts.map +1 -0
  106. package/dist/layout/primitives/Stack.js +34 -0
  107. package/dist/layout/primitives/Stack.js.map +1 -0
  108. package/dist/layout/primitives/index.d.ts +30 -0
  109. package/dist/layout/primitives/index.d.ts.map +1 -0
  110. package/dist/layout/primitives/index.js +22 -0
  111. package/dist/layout/primitives/index.js.map +1 -0
  112. package/dist/layout/primitives/tokens.d.ts +48 -0
  113. package/dist/layout/primitives/tokens.d.ts.map +1 -0
  114. package/dist/layout/primitives/tokens.js +54 -0
  115. package/dist/layout/primitives/tokens.js.map +1 -0
  116. package/dist/lib/cv.d.ts +53 -0
  117. package/dist/lib/cv.d.ts.map +1 -0
  118. package/dist/lib/cv.js +39 -0
  119. package/dist/lib/cv.js.map +1 -0
  120. package/dist/views/ApiKeys.js +7 -7
  121. package/dist/views/CollectionList.js +8 -8
  122. package/dist/views/Dashboard.d.ts.map +1 -1
  123. package/dist/views/Dashboard.js +333 -78
  124. package/dist/views/Dashboard.js.map +1 -1
  125. package/dist/views/DocumentEdit.js +3 -3
  126. package/dist/views/ForgotPassword.js +2 -2
  127. package/dist/views/FormEditor.js +5 -5
  128. package/dist/views/FormSubmissions.js +6 -6
  129. package/dist/views/Forms.js +2 -2
  130. package/dist/views/Login.d.ts +16 -1
  131. package/dist/views/Login.d.ts.map +1 -1
  132. package/dist/views/Login.js +17 -7
  133. package/dist/views/Login.js.map +1 -1
  134. package/dist/views/MediaBrowser.js +16 -16
  135. package/dist/views/PageEditor.js +2 -2
  136. package/dist/views/Pages.js +10 -10
  137. package/dist/views/PostEditor.js +2 -2
  138. package/dist/views/Posts.js +4 -4
  139. package/dist/views/Redirects.js +4 -4
  140. package/dist/views/ResetPassword.js +2 -2
  141. package/dist/views/SEO.js +6 -6
  142. package/dist/views/ScriptTagEditor.js +4 -4
  143. package/dist/views/ScriptTags.js +2 -2
  144. package/dist/views/Settings.d.ts.map +1 -1
  145. package/dist/views/Settings.js +9 -8
  146. package/dist/views/Settings.js.map +1 -1
  147. package/dist/views/SetupWizard.js +2 -2
  148. package/dist/views/Users.js +4 -4
  149. package/dist/views/page-builder/AIBlockAssist.js +1 -1
  150. package/dist/views/page-builder/AIGenerateDialog.js +10 -10
  151. package/dist/views/page-builder/BlockEditor.js +10 -10
  152. package/dist/views/page-builder/BlockPicker.js +4 -4
  153. package/dist/views/page-builder/BottomBar.js +1 -1
  154. package/dist/views/page-builder/BuilderToolbar.js +2 -2
  155. package/dist/views/page-builder/ContextPanel.js +2 -2
  156. package/dist/views/page-builder/DesignScore.js +9 -9
  157. package/dist/views/page-builder/NodeSettings.js +8 -8
  158. package/dist/views/page-builder/PageBuilder.js +3 -3
  159. package/dist/views/page-builder/PageSettings.js +1 -1
  160. package/dist/views/page-builder/PageTemplates.js +2 -2
  161. package/dist/views/page-builder/SEOPanel.js +13 -13
  162. package/dist/views/page-builder/SavedSections.js +5 -5
  163. package/dist/views/page-builder/TemplatePicker.js +2 -2
  164. package/dist/views/page-builder/block-renderers/CTAPreview.js +5 -5
  165. package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
  166. package/dist/views/page-builder/block-renderers/CodePreview.js +1 -1
  167. package/dist/views/page-builder/block-renderers/FAQPreview.js +3 -3
  168. package/dist/views/page-builder/block-renderers/FallbackPreview.js +1 -1
  169. package/dist/views/page-builder/block-renderers/FormPreview.js +3 -3
  170. package/dist/views/page-builder/block-renderers/GalleryPreview.js +5 -5
  171. package/dist/views/page-builder/block-renderers/HeroPreview.js +3 -3
  172. package/dist/views/page-builder/block-renderers/ImagePreview.js +3 -3
  173. package/dist/views/page-builder/block-renderers/TextPreview.js +3 -3
  174. package/dist/views/page-builder/block-renderers/VideoPreview.js +4 -4
  175. package/dist/views/page-builder/canvas/BlockRenderer.js +1 -1
  176. package/dist/views/page-builder/canvas/BuilderCanvas.js +3 -3
  177. package/dist/views/page-builder/canvas/ColumnRenderer.js +2 -2
  178. package/dist/views/page-builder/canvas/ContainerRenderer.js +2 -2
  179. package/dist/views/page-builder/canvas/RowRenderer.js +2 -2
  180. package/dist/views/page-builder/canvas/SectionRenderer.js +2 -2
  181. package/package.json +6 -2
  182. package/src/AdminRoot.tsx +21 -11
  183. package/src/__tests__/layout/primitives.test.ts +37 -0
  184. package/src/__tests__/lib/cv.test.ts +74 -0
  185. package/src/assets/actuate-logo.tsx +72 -0
  186. package/src/components/Breadcrumbs.tsx +6 -6
  187. package/src/components/CommandPalette.tsx +34 -34
  188. package/src/components/ContentOverviewChart.tsx +3 -3
  189. package/src/components/ErrorBoundary.tsx +3 -3
  190. package/src/components/FocalPointPicker.tsx +4 -4
  191. package/src/components/FolderTree.tsx +38 -38
  192. package/src/components/LivePreview.tsx +16 -16
  193. package/src/components/LocaleSwitcher.tsx +7 -7
  194. package/src/components/MediaPickerModal.tsx +21 -21
  195. package/src/components/PresenceIndicator.tsx +2 -2
  196. package/src/components/SEOConfigPanel.tsx +582 -0
  197. package/src/components/SEOPanel.tsx +46 -46
  198. package/src/components/SEOPerformance.tsx +21 -21
  199. package/src/components/SchedulePublishDialog.tsx +4 -4
  200. package/src/components/SharePreviewLinkDialog.tsx +1 -1
  201. package/src/components/TipTapEditor.tsx +33 -33
  202. package/src/components/VersionHistory.tsx +16 -16
  203. package/src/components/ui/Badge.tsx +66 -14
  204. package/src/components/ui/Button.tsx +70 -33
  205. package/src/components/ui/Card.tsx +101 -0
  206. package/src/components/ui/DataTable.tsx +1 -1
  207. package/src/components/ui/Input.tsx +35 -0
  208. package/src/components/ui/SearchInput.tsx +4 -4
  209. package/src/components/ui/Select.tsx +56 -0
  210. package/src/components/ui/Toast.tsx +1 -1
  211. package/src/components/ui/index.ts +18 -4
  212. package/src/fields/BlockBuilderField.tsx +3 -3
  213. package/src/fields/DateField.tsx +1 -1
  214. package/src/fields/RelationshipField.tsx +10 -10
  215. package/src/fields/TextField.tsx +1 -1
  216. package/src/index.ts +28 -0
  217. package/src/layout/Header.tsx +28 -28
  218. package/src/layout/Layout.tsx +39 -46
  219. package/src/layout/Sidebar.tsx +37 -64
  220. package/src/layout/primitives/AdminShell.tsx +118 -0
  221. package/src/layout/primitives/Box.tsx +30 -0
  222. package/src/layout/primitives/Cluster.tsx +74 -0
  223. package/src/layout/primitives/Grid.tsx +120 -0
  224. package/src/layout/primitives/PageContainer.tsx +96 -0
  225. package/src/layout/primitives/Split.tsx +73 -0
  226. package/src/layout/primitives/Stack.tsx +67 -0
  227. package/src/layout/primitives/index.ts +36 -0
  228. package/src/layout/primitives/tokens.ts +76 -0
  229. package/src/lib/cv.ts +96 -0
  230. package/src/styles/build-input.css +1 -1
  231. package/src/views/ApiKeys.tsx +57 -57
  232. package/src/views/CollectionList.tsx +30 -30
  233. package/src/views/Dashboard.tsx +737 -186
  234. package/src/views/DocumentEdit.tsx +9 -9
  235. package/src/views/ForgotPassword.tsx +18 -18
  236. package/src/views/FormEditor.tsx +75 -75
  237. package/src/views/FormSubmissions.tsx +76 -76
  238. package/src/views/Forms.tsx +27 -27
  239. package/src/views/Login.tsx +65 -25
  240. package/src/views/MediaBrowser.tsx +127 -127
  241. package/src/views/PageEditor.tsx +25 -25
  242. package/src/views/Pages.tsx +59 -59
  243. package/src/views/PostEditor.tsx +37 -37
  244. package/src/views/Posts.tsx +48 -48
  245. package/src/views/Redirects.tsx +21 -21
  246. package/src/views/ResetPassword.tsx +28 -28
  247. package/src/views/SEO.tsx +144 -144
  248. package/src/views/ScriptTagEditor.tsx +24 -24
  249. package/src/views/ScriptTags.tsx +10 -10
  250. package/src/views/Settings.tsx +88 -80
  251. package/src/views/SetupWizard.tsx +28 -28
  252. package/src/views/Users.tsx +20 -20
  253. package/src/views/page-builder/AIBlockAssist.tsx +1 -1
  254. package/src/views/page-builder/AIGenerateDialog.tsx +63 -63
  255. package/src/views/page-builder/BlockEditor.tsx +26 -26
  256. package/src/views/page-builder/BlockPicker.tsx +22 -22
  257. package/src/views/page-builder/BottomBar.tsx +8 -8
  258. package/src/views/page-builder/BuilderToolbar.tsx +17 -17
  259. package/src/views/page-builder/ContextPanel.tsx +3 -3
  260. package/src/views/page-builder/DesignScore.tsx +21 -21
  261. package/src/views/page-builder/NodeSettings.tsx +27 -27
  262. package/src/views/page-builder/PageBuilder.tsx +11 -11
  263. package/src/views/page-builder/PageSettings.tsx +4 -4
  264. package/src/views/page-builder/PageTemplates.tsx +18 -18
  265. package/src/views/page-builder/SEOPanel.tsx +53 -53
  266. package/src/views/page-builder/SavedSections.tsx +37 -37
  267. package/src/views/page-builder/TemplatePicker.tsx +17 -17
  268. package/src/views/page-builder/block-renderers/CTAPreview.tsx +13 -13
  269. package/src/views/page-builder/block-renderers/CardsPreview.tsx +5 -5
  270. package/src/views/page-builder/block-renderers/CodePreview.tsx +6 -6
  271. package/src/views/page-builder/block-renderers/FAQPreview.tsx +13 -13
  272. package/src/views/page-builder/block-renderers/FallbackPreview.tsx +3 -3
  273. package/src/views/page-builder/block-renderers/FormPreview.tsx +20 -20
  274. package/src/views/page-builder/block-renderers/GalleryPreview.tsx +8 -8
  275. package/src/views/page-builder/block-renderers/HeroPreview.tsx +16 -16
  276. package/src/views/page-builder/block-renderers/ImagePreview.tsx +4 -4
  277. package/src/views/page-builder/block-renderers/TextPreview.tsx +14 -14
  278. package/src/views/page-builder/block-renderers/VideoPreview.tsx +12 -12
  279. package/src/views/page-builder/canvas/BlockRenderer.tsx +4 -4
  280. package/src/views/page-builder/canvas/BuilderCanvas.tsx +6 -6
  281. package/src/views/page-builder/canvas/ColumnRenderer.tsx +3 -3
  282. package/src/views/page-builder/canvas/ContainerRenderer.tsx +2 -2
  283. package/src/views/page-builder/canvas/RowRenderer.tsx +2 -2
  284. 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
  )
@@ -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
@@ -158,12 +158,12 @@ export function RelationshipField({
158
158
  onClick={() => handleToggle(opt.id)}
159
159
  className={`flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-[var(--accent)] ${isSelected ? 'font-medium' : ''}`}
160
160
  >
161
- <div className="flex-1 min-w-0 text-left">
161
+ <div className="min-w-0 flex-1 text-left">
162
162
  <div className="flex items-center gap-2">
163
163
  <span className="truncate">{getDocTitle(opt)}</span>
164
164
  {isSelected && <span className="shrink-0 text-[var(--primary)]">&#10003;</span>}
165
165
  </div>
166
- <div className="flex items-center gap-2 mt-0.5">
166
+ <div className="mt-0.5 flex items-center gap-2">
167
167
  <span className="text-[10px] text-[var(--muted-foreground)]">{collection}</span>
168
168
  {opt.updatedAt && (
169
169
  <span className="text-[10px] text-[var(--muted-foreground)]">
@@ -201,7 +201,7 @@ export function RelationshipField({
201
201
  onClick={() => handleRemove(item.id)}
202
202
  className="hover:text-[var(--destructive)]"
203
203
  >
204
- <X className="w-3 h-3" />
204
+ <X className="h-3 w-3" />
205
205
  </button>
206
206
  </span>
207
207
  ))}
@@ -209,26 +209,26 @@ export function RelationshipField({
209
209
  )}
210
210
 
211
211
  <div className="relative">
212
- <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--muted-foreground)] pointer-events-none" />
212
+ <Search className="pointer-events-none absolute top-1/2 left-2.5 h-4 w-4 -translate-y-1/2 text-[var(--muted-foreground)]" />
213
213
  <input
214
214
  type="text"
215
215
  value={searchTerm}
216
216
  onChange={(e) => handleSearch(e.target.value)}
217
217
  onFocus={() => setOpen(true)}
218
218
  placeholder={`Search ${relationTo}...`}
219
- className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] pl-8 pr-3 py-2 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
219
+ className="w-full rounded-md border border-[var(--border)] bg-[var(--input-background)] py-2 pr-3 pl-8 text-sm outline-none focus:ring-2 focus:ring-[var(--ring)]"
220
220
  />
221
221
  {loading && (
222
- <Loader2 className="absolute right-2.5 top-1/2 -translate-y-1/2 w-4 h-4 animate-spin text-[var(--muted-foreground)]" />
222
+ <Loader2 className="absolute top-1/2 right-2.5 h-4 w-4 -translate-y-1/2 animate-spin text-[var(--muted-foreground)]" />
223
223
  )}
224
224
 
225
225
  {open && (
226
- <ul className="absolute z-50 mt-1 w-full max-h-60 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--popover)] py-1 shadow-lg">
226
+ <ul className="absolute z-50 mt-1 max-h-60 w-full overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--popover)] py-1 shadow-lg">
227
227
  {unselectedItems.map((opt) => (
228
228
  <li key={opt.id}>{renderOption(opt, false)}</li>
229
229
  ))}
230
230
  {selectedItems.length > 0 && unselectedItems.length > 0 && (
231
- <li className="border-t border-[var(--border)] my-1" />
231
+ <li className="my-1 border-t border-[var(--border)]" />
232
232
  )}
233
233
  {selectedItems.map((opt) => (
234
234
  <li key={opt.id}>{renderOption(opt, true)}</li>
@@ -236,7 +236,7 @@ export function RelationshipField({
236
236
  {options.length === 0 && !loading && (
237
237
  <li className="px-3 py-2 text-sm text-[var(--muted-foreground)]">No results</li>
238
238
  )}
239
- <li className="border-t border-[var(--border)] mt-1">
239
+ <li className="mt-1 border-t border-[var(--border)]">
240
240
  <button
241
241
  type="button"
242
242
  onClick={() => {
@@ -245,7 +245,7 @@ export function RelationshipField({
245
245
  }}
246
246
  className="flex w-full items-center gap-2 px-3 py-2 text-sm text-[var(--primary)] hover:bg-[var(--accent)]"
247
247
  >
248
- <Plus className="w-3.5 h-3.5" />
248
+ <Plus className="h-3.5 w-3.5" />
249
249
  Create New
250
250
  </button>
251
251
  </li>
@@ -43,7 +43,7 @@ export function TextField({
43
43
  onChange={(e) => onChange(e.target.value)}
44
44
  required={required}
45
45
  maxLength={maxLength}
46
- className={`w-full rounded-md border bg-[var(--input-background)] px-3 py-2 text-sm outline-none transition-colors focus:ring-2 focus:ring-[var(--ring)] ${
46
+ className={`w-full rounded-md border bg-[var(--input-background)] px-3 py-2 text-sm transition-colors outline-none focus:ring-2 focus:ring-[var(--ring)] ${
47
47
  hasError ? 'border-[var(--destructive)]' : 'border-[var(--border)]'
48
48
  }`}
49
49
  />
package/src/index.ts CHANGED
@@ -8,6 +8,34 @@ export type { SidebarProps } from './layout/Sidebar.js'
8
8
  export { Header } from './layout/Header.js'
9
9
  export type { HeaderProps } from './layout/Header.js'
10
10
 
11
+ // Layout primitives — the sanctioned way to compose admin views.
12
+ // Always prefer these to hand-rolled flex/grid utilities.
13
+ export {
14
+ AdminShell,
15
+ PageContainer,
16
+ Stack,
17
+ Cluster,
18
+ Grid,
19
+ Split,
20
+ Box,
21
+ tokens,
22
+ } from './layout/primitives/index.js'
23
+ export type {
24
+ AdminShellProps,
25
+ PageContainerProps,
26
+ StackProps,
27
+ StackSpace,
28
+ ClusterProps,
29
+ ClusterAlign,
30
+ ClusterJustify,
31
+ GridProps,
32
+ GridResponsive,
33
+ SplitProps,
34
+ BoxProps,
35
+ SpaceToken,
36
+ RadiusToken,
37
+ } from './layout/primitives/index.js'
38
+
11
39
  export { Dashboard } from './views/Dashboard.js'
12
40
  export { Posts } from './views/Posts.js'
13
41
  export { Pages } from './views/Pages.js'
@@ -24,100 +24,100 @@ export function Header({ onToggleSidebar, session, onNavigate }: HeaderProps) {
24
24
 
25
25
  return (
26
26
  <>
27
- <header className="h-14 border-b border-border bg-background flex items-center justify-between px-4 gap-4">
27
+ <header className="border-border bg-background flex h-14 items-center justify-between gap-4 border-b px-4">
28
28
  <button
29
29
  onClick={onToggleSidebar}
30
- className="lg:hidden p-2 hover:bg-accent rounded-lg transition-colors"
30
+ className="hover:bg-accent rounded-lg p-2 transition-colors md:hidden"
31
31
  aria-label="Toggle sidebar"
32
32
  >
33
- <Menu className="w-5 h-5 text-foreground" strokeWidth={2} />
33
+ <Menu className="text-foreground h-5 w-5" strokeWidth={2} />
34
34
  </button>
35
35
 
36
- <div className="flex items-center lg:hidden">
37
- <span className="text-lg font-semibold text-foreground">Actuate</span>
36
+ <div className="flex items-center md:hidden">
37
+ <span className="text-foreground text-lg font-semibold">Actuate</span>
38
38
  </div>
39
39
 
40
40
  <div className="flex-1" />
41
41
 
42
42
  <div className="flex items-center gap-2 sm:gap-3">
43
- <div className="hidden md:block relative">
44
- <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
43
+ <div className="relative hidden md:block">
44
+ <Search className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
45
45
  <input
46
46
  type="text"
47
47
  placeholder="Search... (⌘K)"
48
48
  value={searchQuery}
49
49
  onChange={(e) => setSearchQuery(e.target.value)}
50
50
  onFocus={() => setShowCommandPalette(true)}
51
- className="w-64 pl-9 pr-3 py-1.5 text-sm border border-border rounded-lg bg-input-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
51
+ className="border-border bg-input-background text-foreground focus:ring-ring w-64 rounded-lg border py-1.5 pr-3 pl-9 text-sm focus:border-transparent focus:ring-2 focus:outline-none"
52
52
  />
53
53
  </div>
54
54
 
55
55
  <button
56
56
  onClick={() => setShowCommandPalette(true)}
57
- className="md:hidden p-2 hover:bg-accent rounded-lg transition-colors"
57
+ className="hover:bg-accent rounded-lg p-2 transition-colors md:hidden"
58
58
  aria-label="Search"
59
59
  >
60
- <Search className="w-5 h-5 text-muted-foreground" />
60
+ <Search className="text-muted-foreground h-5 w-5" />
61
61
  </button>
62
62
 
63
63
  <LocaleSwitcher />
64
64
 
65
65
  <button
66
66
  onClick={toggleTheme}
67
- className="p-2 hover:bg-accent rounded-lg transition-colors"
67
+ className="hover:bg-accent rounded-lg p-2 transition-colors"
68
68
  aria-label="Toggle theme"
69
69
  >
70
70
  {resolvedTheme === 'dark' ? (
71
- <Sun className="w-5 h-5 text-muted-foreground" />
71
+ <Sun className="text-muted-foreground h-5 w-5" />
72
72
  ) : (
73
- <Moon className="w-5 h-5 text-muted-foreground" />
73
+ <Moon className="text-muted-foreground h-5 w-5" />
74
74
  )}
75
75
  </button>
76
76
 
77
77
  <button
78
- className="p-2 hover:bg-accent rounded-lg transition-colors relative"
78
+ className="hover:bg-accent relative rounded-lg p-2 transition-colors"
79
79
  aria-label="Notifications"
80
80
  >
81
- <Bell className="w-5 h-5 text-muted-foreground" />
82
- <span className="absolute top-1.5 right-1.5 w-2 h-2 bg-destructive rounded-full" />
81
+ <Bell className="text-muted-foreground h-5 w-5" />
82
+ <span className="bg-destructive absolute top-1.5 right-1.5 h-2 w-2 rounded-full" />
83
83
  </button>
84
84
 
85
85
  <DropdownMenu.Root>
86
86
  <DropdownMenu.Trigger asChild>
87
- <button className="flex items-center gap-2 p-1.5 hover:bg-accent rounded-lg transition-colors">
88
- <div className="w-8 h-8 bg-linear-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
89
- <span className="text-white text-sm font-medium">
87
+ <button className="hover:bg-accent flex items-center gap-2 rounded-lg p-1.5 transition-colors">
88
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-linear-to-br from-blue-500 to-purple-600">
89
+ <span className="text-sm font-medium text-white">
90
90
  {session?.user?.name?.charAt(0)?.toUpperCase() ?? 'A'}
91
91
  </span>
92
92
  </div>
93
- <ChevronDown className="w-4 h-4 text-muted-foreground hidden sm:block" />
93
+ <ChevronDown className="text-muted-foreground hidden h-4 w-4 sm:block" />
94
94
  </button>
95
95
  </DropdownMenu.Trigger>
96
96
 
97
97
  <DropdownMenu.Portal>
98
98
  <DropdownMenu.Content
99
- className="min-w-[200px] bg-popover text-popover-foreground rounded-lg border border-border shadow-lg p-1 z-50"
99
+ className="bg-popover text-popover-foreground border-border z-50 min-w-[200px] rounded-lg border p-1 shadow-lg"
100
100
  align="end"
101
101
  sideOffset={5}
102
102
  >
103
- <div className="px-3 py-2 border-b border-border">
103
+ <div className="border-border border-b px-3 py-2">
104
104
  <p className="text-sm font-medium">{session?.user?.name ?? 'Admin User'}</p>
105
- <p className="text-xs text-muted-foreground">
105
+ <p className="text-muted-foreground text-xs">
106
106
  {session?.user?.email ?? 'admin@example.com'}
107
107
  </p>
108
108
  </div>
109
- <DropdownMenu.Item className="flex items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded cursor-pointer outline-none">
110
- <User className="w-4 h-4" />
109
+ <DropdownMenu.Item className="hover:bg-accent flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm outline-none">
110
+ <User className="h-4 w-4" />
111
111
  Profile
112
112
  </DropdownMenu.Item>
113
113
  <DropdownMenu.Item
114
- className="flex items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded cursor-pointer outline-none"
114
+ className="hover:bg-accent flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm outline-none"
115
115
  onSelect={() => onNavigate('/settings')}
116
116
  >
117
117
  Settings
118
118
  </DropdownMenu.Item>
119
- <DropdownMenu.Separator className="h-px bg-border my-1" />
120
- <DropdownMenu.Item className="flex items-center gap-2 px-3 py-2 text-sm text-destructive hover:bg-accent rounded cursor-pointer outline-none">
119
+ <DropdownMenu.Separator className="bg-border my-1 h-px" />
120
+ <DropdownMenu.Item className="text-destructive hover:bg-accent flex cursor-pointer items-center gap-2 rounded px-3 py-2 text-sm outline-none">
121
121
  Logout
122
122
  </DropdownMenu.Item>
123
123
  </DropdownMenu.Content>
@@ -4,6 +4,7 @@ import { useState, useEffect, type ReactNode } from 'react'
4
4
  import { Sidebar } from './Sidebar.js'
5
5
  import { Header } from './Header.js'
6
6
  import { Breadcrumbs } from '../components/Breadcrumbs.js'
7
+ import { AdminShell } from './primitives/AdminShell.js'
7
8
  import { Toaster } from 'sonner'
8
9
 
9
10
  export interface LayoutProps {
@@ -14,6 +15,20 @@ export interface LayoutProps {
14
15
  children: ReactNode
15
16
  }
16
17
 
18
+ /**
19
+ * Layout — thin shell that wires Sidebar/Header/Breadcrumbs into the
20
+ * `<AdminShell>` primitive.
21
+ *
22
+ * `AdminShell` (in `./primitives/AdminShell.tsx`) owns the actual layout
23
+ * algorithm (CSS Grid on desktop, fixed slide-in overlay on mobile,
24
+ * JS-driven `matchMedia` breakpoint) so that every layout decision lives
25
+ * in one place. Per-product chrome (Sidebar, Header, Breadcrumbs)
26
+ * composes on top of it.
27
+ *
28
+ * DO NOT re-implement layout logic in this file — extend `AdminShell`
29
+ * instead. The previous flex + `fixed`↔`static` toggle caused recurring
30
+ * sidebar-overlap bugs; the grid-based primitive is the contract.
31
+ */
17
32
  export function Layout({ config, session, currentPath, onNavigate, children }: LayoutProps) {
18
33
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
19
34
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
@@ -22,54 +37,32 @@ export function Layout({ config, session, currentPath, onNavigate, children }: L
22
37
  setMobileSidebarOpen(false)
23
38
  }, [currentPath])
24
39
 
25
- useEffect(() => {
26
- const handleResize = () => {
27
- if (window.innerWidth < 1024) {
28
- setMobileSidebarOpen(false)
29
- }
30
- }
31
- window.addEventListener('resize', handleResize)
32
- return () => window.removeEventListener('resize', handleResize)
33
- }, [])
34
-
35
40
  return (
36
- <div className="h-screen flex overflow-hidden bg-background text-foreground">
41
+ <>
37
42
  <Toaster position="bottom-right" />
38
-
39
- {mobileSidebarOpen && (
40
- <div
41
- className="fixed inset-0 bg-black/50 z-40 lg:hidden"
42
- onClick={() => setMobileSidebarOpen(false)}
43
- />
44
- )}
45
-
46
- <div
47
- className={`fixed lg:static inset-y-0 left-0 z-50 transform transition-transform duration-300 lg:transform-none ${
48
- mobileSidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
49
- }`}
43
+ <AdminShell
44
+ mobileSidebarOpen={mobileSidebarOpen}
45
+ onMobileSidebarClose={() => setMobileSidebarOpen(false)}
46
+ sidebar={
47
+ <Sidebar
48
+ collapsed={sidebarCollapsed}
49
+ onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
50
+ currentPath={currentPath}
51
+ onNavigate={onNavigate}
52
+ config={config}
53
+ />
54
+ }
55
+ header={
56
+ <Header
57
+ onToggleSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
58
+ session={session}
59
+ onNavigate={onNavigate}
60
+ />
61
+ }
62
+ breadcrumbs={<Breadcrumbs currentPath={currentPath} onNavigate={onNavigate} />}
50
63
  >
51
- <Sidebar
52
- collapsed={sidebarCollapsed}
53
- onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
54
- currentPath={currentPath}
55
- onNavigate={onNavigate}
56
- config={config}
57
- />
58
- </div>
59
-
60
- <div className="flex-1 flex flex-col overflow-hidden">
61
- <Header
62
- onToggleSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
63
- session={session}
64
- onNavigate={onNavigate}
65
- />
66
-
67
- <Breadcrumbs currentPath={currentPath} onNavigate={onNavigate} />
68
-
69
- <main className="flex-1 overflow-auto bg-background">
70
- <div className="h-full">{children}</div>
71
- </main>
72
- </div>
73
- </div>
64
+ <div className="h-full">{children}</div>
65
+ </AdminShell>
66
+ </>
74
67
  )
75
68
  }