@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
@@ -1,47 +1,79 @@
1
1
  'use client'
2
2
 
3
- import { type ButtonHTMLAttributes, type ReactNode } from 'react'
3
+ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'
4
+ import { cv, type VariantProps } from '../../lib/cv.js'
4
5
 
5
- type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost'
6
- type ButtonSize = 'sm' | 'md' | 'lg'
6
+ const button = cv(
7
+ // Base classes apply to every variant.
8
+ 'focus-visible:ring-offset-background inline-flex items-center justify-center gap-2 rounded-[var(--radius)] font-medium transition-colors focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ primary: 'bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90',
13
+ secondary: 'bg-[var(--secondary)] text-[var(--secondary-foreground)] hover:opacity-80',
14
+ danger: 'bg-[var(--destructive)] text-[var(--destructive-foreground)] hover:opacity-90',
15
+ ghost: 'bg-transparent text-[var(--foreground)] hover:bg-[var(--accent)]',
16
+ outline:
17
+ 'border border-[var(--border)] bg-transparent text-[var(--foreground)] hover:bg-[var(--accent)]',
18
+ link: 'bg-transparent text-[var(--primary)] underline-offset-4 hover:underline',
19
+ },
20
+ size: {
21
+ xs: 'h-7 px-2 text-xs',
22
+ sm: 'h-8 px-2.5 text-xs',
23
+ md: 'h-9 px-4 text-sm',
24
+ lg: 'h-10 px-6 text-base',
25
+ icon: 'h-9 w-9 p-0',
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: 'primary',
30
+ size: 'md',
31
+ },
32
+ },
33
+ )
7
34
 
8
- export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
9
- variant?: ButtonVariant
10
- size?: ButtonSize
11
- loading?: boolean
12
- children: ReactNode
13
- }
14
-
15
- const variantClasses: Record<ButtonVariant, string> = {
16
- primary: 'bg-[var(--primary)] text-[var(--primary-foreground)] hover:opacity-90',
17
- secondary: 'bg-[var(--secondary)] text-[var(--secondary-foreground)] hover:opacity-80',
18
- danger: 'bg-[var(--destructive)] text-[var(--destructive-foreground)] hover:opacity-90',
19
- ghost: 'bg-transparent hover:bg-[var(--accent)] text-[var(--foreground)]',
20
- }
35
+ export type ButtonVariants = VariantProps<typeof button>
21
36
 
22
- const sizeClasses: Record<ButtonSize, string> = {
23
- sm: 'px-2.5 py-1 text-xs',
24
- md: 'px-4 py-2 text-sm',
25
- lg: 'px-6 py-2.5 text-base',
37
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, ButtonVariants {
38
+ loading?: boolean
39
+ /** Stretch the button to fill its container width. */
40
+ full?: boolean
41
+ /** Icon rendered before the children. Use with `size="icon"` for icon-only buttons. */
42
+ leftIcon?: ReactNode
43
+ /** Icon rendered after the children. */
44
+ rightIcon?: ReactNode
45
+ children?: ReactNode
26
46
  }
27
47
 
28
- export function Button({
29
- variant = 'primary',
30
- size = 'md',
31
- loading = false,
32
- disabled,
33
- children,
34
- className = '',
35
- ...rest
36
- }: ButtonProps) {
48
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
49
+ {
50
+ variant,
51
+ size,
52
+ full,
53
+ loading = false,
54
+ disabled,
55
+ leftIcon,
56
+ rightIcon,
57
+ children,
58
+ className,
59
+ ...rest
60
+ },
61
+ ref,
62
+ ) {
37
63
  return (
38
64
  <button
65
+ ref={ref}
39
66
  disabled={disabled || loading}
40
- className={`inline-flex items-center justify-center gap-2 rounded-[var(--radius)] font-medium transition-colors disabled:pointer-events-none disabled:opacity-50 ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
67
+ aria-busy={loading || undefined}
68
+ className={button({
69
+ variant,
70
+ size,
71
+ class: [full ? 'w-full' : '', className].filter(Boolean).join(' '),
72
+ })}
41
73
  {...rest}
42
74
  >
43
- {loading && (
44
- <svg className="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
75
+ {loading ? (
76
+ <svg className="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none" aria-hidden>
45
77
  <circle
46
78
  className="opacity-25"
47
79
  cx="12"
@@ -56,8 +88,13 @@ export function Button({
56
88
  d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
57
89
  />
58
90
  </svg>
91
+ ) : (
92
+ leftIcon
59
93
  )}
60
94
  {children}
95
+ {!loading && rightIcon}
61
96
  </button>
62
97
  )
63
- }
98
+ })
99
+
100
+ export { button as buttonVariants }
@@ -0,0 +1,101 @@
1
+ import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'
2
+ import { cv, type VariantProps } from '../../lib/cv.js'
3
+
4
+ const card = cv(
5
+ 'rounded-[var(--radius)] border border-[var(--border)] bg-[var(--card)] text-[var(--card-foreground)]',
6
+ {
7
+ variants: {
8
+ padding: {
9
+ none: 'p-0',
10
+ sm: 'p-4',
11
+ md: 'p-6',
12
+ lg: 'p-8',
13
+ },
14
+ elevation: {
15
+ flat: 'shadow-none',
16
+ sm: 'shadow-sm',
17
+ md: 'shadow-md',
18
+ lg: 'shadow-lg',
19
+ },
20
+ },
21
+ defaultVariants: { padding: 'md', elevation: 'sm' },
22
+ },
23
+ )
24
+
25
+ export type CardVariants = VariantProps<typeof card>
26
+
27
+ const INTERACTIVE_CLASS = 'transition-shadow hover:shadow-md focus-within:shadow-md'
28
+
29
+ export interface CardProps extends HTMLAttributes<HTMLDivElement>, CardVariants {
30
+ /** Apply hover/focus elevation transitions (e.g. for clickable cards). */
31
+ interactive?: boolean
32
+ children?: ReactNode
33
+ }
34
+
35
+ export const Card = forwardRef<HTMLDivElement, CardProps>(function Card(
36
+ { padding, elevation, interactive, className, children, ...rest },
37
+ ref,
38
+ ) {
39
+ const extras = [interactive ? INTERACTIVE_CLASS : '', className].filter(Boolean).join(' ')
40
+ return (
41
+ <div ref={ref} className={card({ padding, elevation, class: extras })} {...rest}>
42
+ {children}
43
+ </div>
44
+ )
45
+ })
46
+
47
+ /**
48
+ * Header slot — sits at the top of a card. Pair with CardTitle / CardDescription.
49
+ */
50
+ export function CardHeader({ className = '', children, ...rest }: HTMLAttributes<HTMLDivElement>) {
51
+ return (
52
+ <div className={`flex flex-col space-y-1.5 ${className}`} {...rest}>
53
+ {children}
54
+ </div>
55
+ )
56
+ }
57
+
58
+ export function CardTitle({
59
+ className = '',
60
+ children,
61
+ ...rest
62
+ }: HTMLAttributes<HTMLHeadingElement>) {
63
+ return (
64
+ <h3
65
+ className={`text-lg leading-tight font-semibold tracking-tight text-[var(--card-foreground)] ${className}`}
66
+ {...rest}
67
+ >
68
+ {children}
69
+ </h3>
70
+ )
71
+ }
72
+
73
+ export function CardDescription({
74
+ className = '',
75
+ children,
76
+ ...rest
77
+ }: HTMLAttributes<HTMLParagraphElement>) {
78
+ return (
79
+ <p className={`text-sm text-[var(--muted-foreground)] ${className}`} {...rest}>
80
+ {children}
81
+ </p>
82
+ )
83
+ }
84
+
85
+ export function CardContent({ className = '', children, ...rest }: HTMLAttributes<HTMLDivElement>) {
86
+ return (
87
+ <div className={className} {...rest}>
88
+ {children}
89
+ </div>
90
+ )
91
+ }
92
+
93
+ export function CardFooter({ className = '', children, ...rest }: HTMLAttributes<HTMLDivElement>) {
94
+ return (
95
+ <div className={`flex items-center justify-end gap-2 pt-4 ${className}`} {...rest}>
96
+ {children}
97
+ </div>
98
+ )
99
+ }
100
+
101
+ export { card as cardVariants }
@@ -161,7 +161,7 @@ function RowActionsMenu({ actions, row }: { actions: RowAction[]; row: Row }) {
161
161
  <MoreVerticalIcon />
162
162
  </button>
163
163
  {open && (
164
- <div className="absolute right-0 top-full z-50 mt-1 w-40 rounded-md border border-[var(--border)] bg-[var(--popover)] py-1 shadow-lg">
164
+ <div className="absolute top-full right-0 z-50 mt-1 w-40 rounded-md border border-[var(--border)] bg-[var(--popover)] py-1 shadow-lg">
165
165
  {actions.map((action) => (
166
166
  <button
167
167
  key={action.key}
@@ -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'
@@ -60,6 +88,10 @@ export type { SEOData, SEOPanelProps } from './components/SEOPanel.js'
60
88
  export { LivePreview } from './components/LivePreview.js'
61
89
  export { VersionHistory } from './components/VersionHistory.js'
62
90
  export type { VersionHistoryProps } from './components/VersionHistory.js'
91
+ export { SchedulePublishDialog } from './components/SchedulePublishDialog.js'
92
+ export type { SchedulePublishDialogProps } from './components/SchedulePublishDialog.js'
93
+ export { SharePreviewLinkDialog } from './components/SharePreviewLinkDialog.js'
94
+ export type { SharePreviewLinkDialogProps } from './components/SharePreviewLinkDialog.js'
63
95
  export { MediaPickerModal } from './components/MediaPickerModal.js'
64
96
  export type { MediaPickerModalProps } from './components/MediaPickerModal.js'
65
97
  export { ThemeProvider, useTheme } from './components/ThemeProvider.js'