@actuate-media/cms-admin 0.63.2 → 0.65.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 (245) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/design-system/README.md +53 -0
  3. package/design-system/tokens.css +114 -0
  4. package/design-system/tokens.json +149 -0
  5. package/design-system/tokens.scss +108 -0
  6. package/design-system/tokens.tailwind.cjs +80 -0
  7. package/dist/__tests__/components/ui-primitives.test.d.ts +2 -0
  8. package/dist/__tests__/components/ui-primitives.test.d.ts.map +1 -0
  9. package/dist/__tests__/components/ui-primitives.test.js +45 -0
  10. package/dist/__tests__/components/ui-primitives.test.js.map +1 -0
  11. package/dist/__tests__/layout/primitives.test.js +9 -2
  12. package/dist/__tests__/layout/primitives.test.js.map +1 -1
  13. package/dist/__tests__/views/updates-tab.render.test.js +1 -1
  14. package/dist/__tests__/views/updates-tab.render.test.js.map +1 -1
  15. package/dist/actuate-admin.css +1 -1
  16. package/dist/components/EnabledSwitch.d.ts +2 -5
  17. package/dist/components/EnabledSwitch.d.ts.map +1 -1
  18. package/dist/components/EnabledSwitch.js +4 -7
  19. package/dist/components/EnabledSwitch.js.map +1 -1
  20. package/dist/components/SEOPanel.d.ts.map +1 -1
  21. package/dist/components/SEOPanel.js +2 -1
  22. package/dist/components/SEOPanel.js.map +1 -1
  23. package/dist/components/ThemeProvider.d.ts.map +1 -1
  24. package/dist/components/ThemeProvider.js +5 -3
  25. package/dist/components/ThemeProvider.js.map +1 -1
  26. package/dist/components/forms/FieldsPanel.js +2 -2
  27. package/dist/components/forms/FieldsPanel.js.map +1 -1
  28. package/dist/components/forms/NotificationsPanel.js +2 -2
  29. package/dist/components/forms/NotificationsPanel.js.map +1 -1
  30. package/dist/components/forms/WebhooksPanel.d.ts.map +1 -1
  31. package/dist/components/forms/WebhooksPanel.js +2 -2
  32. package/dist/components/forms/WebhooksPanel.js.map +1 -1
  33. package/dist/components/forms/primitives.d.ts.map +1 -1
  34. package/dist/components/forms/primitives.js +5 -8
  35. package/dist/components/forms/primitives.js.map +1 -1
  36. package/dist/components/posts/badges.d.ts.map +1 -1
  37. package/dist/components/posts/badges.js +7 -13
  38. package/dist/components/posts/badges.js.map +1 -1
  39. package/dist/components/seo/RedirectEditorDrawer.d.ts.map +1 -1
  40. package/dist/components/seo/RedirectEditorDrawer.js +2 -1
  41. package/dist/components/seo/RedirectEditorDrawer.js.map +1 -1
  42. package/dist/components/seo/SeoEditorDrawer.d.ts.map +1 -1
  43. package/dist/components/seo/SeoEditorDrawer.js +3 -2
  44. package/dist/components/seo/SeoEditorDrawer.js.map +1 -1
  45. package/dist/components/seo/primitives.d.ts.map +1 -1
  46. package/dist/components/seo/primitives.js +11 -15
  47. package/dist/components/seo/primitives.js.map +1 -1
  48. package/dist/components/ui/Button.d.ts +1 -1
  49. package/dist/components/ui/Button.js +1 -1
  50. package/dist/components/ui/Button.js.map +1 -1
  51. package/dist/components/ui/Card.d.ts +6 -5
  52. package/dist/components/ui/Card.d.ts.map +1 -1
  53. package/dist/components/ui/Card.js +20 -18
  54. package/dist/components/ui/Card.js.map +1 -1
  55. package/dist/components/ui/FilterPill.d.ts +14 -0
  56. package/dist/components/ui/FilterPill.d.ts.map +1 -0
  57. package/dist/components/ui/FilterPill.js +8 -0
  58. package/dist/components/ui/FilterPill.js.map +1 -0
  59. package/dist/components/ui/Input.d.ts +2 -1
  60. package/dist/components/ui/Input.d.ts.map +1 -1
  61. package/dist/components/ui/Input.js +9 -8
  62. package/dist/components/ui/Input.js.map +1 -1
  63. package/dist/components/ui/SearchInput.d.ts.map +1 -1
  64. package/dist/components/ui/SearchInput.js +2 -1
  65. package/dist/components/ui/SearchInput.js.map +1 -1
  66. package/dist/components/ui/Select.d.ts.map +1 -1
  67. package/dist/components/ui/Select.js +9 -10
  68. package/dist/components/ui/Select.js.map +1 -1
  69. package/dist/components/ui/StatPill.d.ts +18 -0
  70. package/dist/components/ui/StatPill.d.ts.map +1 -0
  71. package/dist/components/ui/StatPill.js +5 -0
  72. package/dist/components/ui/StatPill.js.map +1 -0
  73. package/dist/components/ui/StatusBadge.d.ts +25 -0
  74. package/dist/components/ui/StatusBadge.d.ts.map +1 -0
  75. package/dist/components/ui/StatusBadge.js +41 -0
  76. package/dist/components/ui/StatusBadge.js.map +1 -0
  77. package/dist/components/ui/Toggle.d.ts +31 -0
  78. package/dist/components/ui/Toggle.d.ts.map +1 -0
  79. package/dist/components/ui/Toggle.js +41 -0
  80. package/dist/components/ui/Toggle.js.map +1 -0
  81. package/dist/components/ui/form-control.d.ts +11 -0
  82. package/dist/components/ui/form-control.d.ts.map +1 -0
  83. package/dist/components/ui/form-control.js +11 -0
  84. package/dist/components/ui/form-control.js.map +1 -0
  85. package/dist/components/ui/index.d.ts +9 -0
  86. package/dist/components/ui/index.d.ts.map +1 -1
  87. package/dist/components/ui/index.js +5 -0
  88. package/dist/components/ui/index.js.map +1 -1
  89. package/dist/fields/PropInput.d.ts.map +1 -1
  90. package/dist/fields/PropInput.js +2 -1
  91. package/dist/fields/PropInput.js.map +1 -1
  92. package/dist/fields/ToggleField.d.ts.map +1 -1
  93. package/dist/fields/ToggleField.js +2 -1
  94. package/dist/fields/ToggleField.js.map +1 -1
  95. package/dist/layout/Header.d.ts.map +1 -1
  96. package/dist/layout/Header.js +5 -2
  97. package/dist/layout/Header.js.map +1 -1
  98. package/dist/layout/Sidebar.js +1 -1
  99. package/dist/layout/Sidebar.js.map +1 -1
  100. package/dist/layout/primitives/PageContainer.d.ts.map +1 -1
  101. package/dist/layout/primitives/PageContainer.js +1 -1
  102. package/dist/layout/primitives/PageContainer.js.map +1 -1
  103. package/dist/layout/primitives/tokens.d.ts +10 -4
  104. package/dist/layout/primitives/tokens.d.ts.map +1 -1
  105. package/dist/layout/primitives/tokens.js +10 -4
  106. package/dist/layout/primitives/tokens.js.map +1 -1
  107. package/dist/views/Dashboard.js +1 -1
  108. package/dist/views/Dashboard.js.map +1 -1
  109. package/dist/views/FormEditor.d.ts.map +1 -1
  110. package/dist/views/FormEditor.js +2 -1
  111. package/dist/views/FormEditor.js.map +1 -1
  112. package/dist/views/FormEntries.js +1 -1
  113. package/dist/views/FormEntries.js.map +1 -1
  114. package/dist/views/FormSubmissions.js +1 -1
  115. package/dist/views/FormSubmissions.js.map +1 -1
  116. package/dist/views/Forms.js +3 -3
  117. package/dist/views/Forms.js.map +1 -1
  118. package/dist/views/MediaBrowser.d.ts.map +1 -1
  119. package/dist/views/MediaBrowser.js +3 -2
  120. package/dist/views/MediaBrowser.js.map +1 -1
  121. package/dist/views/Pages/PageTagManager.d.ts.map +1 -1
  122. package/dist/views/Pages/PageTagManager.js +1 -1
  123. package/dist/views/Pages/PageTagManager.js.map +1 -1
  124. package/dist/views/Pages/PagesListView.js +1 -1
  125. package/dist/views/Pages/PagesListView.js.map +1 -1
  126. package/dist/views/Pages.js +1 -1
  127. package/dist/views/Pages.js.map +1 -1
  128. package/dist/views/Posts/NewPostPicker.d.ts.map +1 -1
  129. package/dist/views/Posts/NewPostPicker.js +1 -1
  130. package/dist/views/Posts/NewPostPicker.js.map +1 -1
  131. package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -1
  132. package/dist/views/Posts/NewPostTypeInstructions.js +1 -1
  133. package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -1
  134. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  135. package/dist/views/Posts/PostTypesView.js +1 -1
  136. package/dist/views/Posts/PostTypesView.js.map +1 -1
  137. package/dist/views/Posts/PostsListView.js +1 -1
  138. package/dist/views/Posts/PostsListView.js.map +1 -1
  139. package/dist/views/Profile.js +1 -1
  140. package/dist/views/Profile.js.map +1 -1
  141. package/dist/views/Redirects.js +1 -1
  142. package/dist/views/Redirects.js.map +1 -1
  143. package/dist/views/SEO.js +1 -1
  144. package/dist/views/SEO.js.map +1 -1
  145. package/dist/views/ScriptTagEditor.d.ts.map +1 -1
  146. package/dist/views/ScriptTagEditor.js +1 -1
  147. package/dist/views/ScriptTagEditor.js.map +1 -1
  148. package/dist/views/ScriptTags.d.ts.map +1 -1
  149. package/dist/views/ScriptTags.js +2 -1
  150. package/dist/views/ScriptTags.js.map +1 -1
  151. package/dist/views/Settings.js +1 -1
  152. package/dist/views/Settings.js.map +1 -1
  153. package/dist/views/Users.d.ts.map +1 -1
  154. package/dist/views/Users.js +2 -6
  155. package/dist/views/Users.js.map +1 -1
  156. package/dist/views/page-builder/BlockEditor.d.ts.map +1 -1
  157. package/dist/views/page-builder/BlockEditor.js +2 -2
  158. package/dist/views/page-builder/BlockEditor.js.map +1 -1
  159. package/dist/views/page-builder/BottomBar.d.ts.map +1 -1
  160. package/dist/views/page-builder/BottomBar.js +2 -2
  161. package/dist/views/page-builder/BottomBar.js.map +1 -1
  162. package/dist/views/page-builder/NodeSettings.js +3 -3
  163. package/dist/views/page-builder/NodeSettings.js.map +1 -1
  164. package/dist/views/page-builder/PageTemplates.js +1 -1
  165. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  166. package/dist/views/page-builder/SavedSections.js +2 -2
  167. package/dist/views/page-builder/SavedSections.js.map +1 -1
  168. package/dist/views/page-builder/Widgets.js +1 -1
  169. package/dist/views/page-builder/Widgets.js.map +1 -1
  170. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  171. package/dist/views/page-editor/SectionInspector.js +2 -1
  172. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  173. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -1
  174. package/dist/views/post-editor/PostHeaderPanel.js +2 -1
  175. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
  176. package/dist/views/seo/TechnicalTab.d.ts.map +1 -1
  177. package/dist/views/seo/TechnicalTab.js +2 -2
  178. package/dist/views/seo/TechnicalTab.js.map +1 -1
  179. package/dist/views/settings/components.d.ts.map +1 -1
  180. package/dist/views/settings/components.js +4 -2
  181. package/dist/views/settings/components.js.map +1 -1
  182. package/package.json +9 -4
  183. package/src/__tests__/components/ui-primitives.test.tsx +58 -0
  184. package/src/__tests__/layout/primitives.test.ts +10 -2
  185. package/src/__tests__/views/updates-tab.render.test.tsx +2 -1
  186. package/src/components/EnabledSwitch.tsx +4 -22
  187. package/src/components/SEOPanel.tsx +2 -11
  188. package/src/components/ThemeProvider.tsx +5 -3
  189. package/src/components/forms/FieldsPanel.tsx +4 -6
  190. package/src/components/forms/NotificationsPanel.tsx +2 -9
  191. package/src/components/forms/WebhooksPanel.tsx +4 -6
  192. package/src/components/forms/primitives.tsx +6 -16
  193. package/src/components/posts/badges.tsx +8 -21
  194. package/src/components/seo/RedirectEditorDrawer.tsx +12 -16
  195. package/src/components/seo/SeoEditorDrawer.tsx +16 -24
  196. package/src/components/seo/primitives.tsx +18 -34
  197. package/src/components/ui/Button.tsx +1 -1
  198. package/src/components/ui/Card.tsx +35 -18
  199. package/src/components/ui/FilterPill.tsx +43 -0
  200. package/src/components/ui/Input.tsx +15 -17
  201. package/src/components/ui/SearchInput.tsx +9 -4
  202. package/src/components/ui/Select.tsx +15 -18
  203. package/src/components/ui/StatPill.tsx +37 -0
  204. package/src/components/ui/StatusBadge.tsx +66 -0
  205. package/src/components/ui/Toggle.tsx +85 -0
  206. package/src/components/ui/form-control.ts +12 -0
  207. package/src/components/ui/index.ts +9 -0
  208. package/src/fields/PropInput.tsx +6 -15
  209. package/src/fields/ToggleField.tsx +3 -15
  210. package/src/layout/Header.tsx +6 -2
  211. package/src/layout/Sidebar.tsx +3 -3
  212. package/src/layout/primitives/PageContainer.tsx +1 -5
  213. package/src/layout/primitives/tokens.ts +11 -4
  214. package/src/styles/actuate-tokens.css +97 -0
  215. package/src/styles/theme.css +88 -156
  216. package/src/views/Dashboard.tsx +1 -1
  217. package/src/views/FormEditor.tsx +6 -9
  218. package/src/views/FormEntries.tsx +1 -1
  219. package/src/views/FormSubmissions.tsx +1 -1
  220. package/src/views/Forms.tsx +5 -7
  221. package/src/views/MediaBrowser.tsx +4 -7
  222. package/src/views/Pages/PageTagManager.tsx +1 -3
  223. package/src/views/Pages/PagesListView.tsx +1 -1
  224. package/src/views/Pages.tsx +1 -1
  225. package/src/views/Posts/NewPostPicker.tsx +1 -3
  226. package/src/views/Posts/NewPostTypeInstructions.tsx +1 -3
  227. package/src/views/Posts/PostTypesView.tsx +1 -3
  228. package/src/views/Posts/PostsListView.tsx +1 -1
  229. package/src/views/Profile.tsx +1 -1
  230. package/src/views/Redirects.tsx +1 -1
  231. package/src/views/SEO.tsx +1 -1
  232. package/src/views/ScriptTagEditor.tsx +1 -3
  233. package/src/views/ScriptTags.tsx +17 -1
  234. package/src/views/Settings.tsx +1 -1
  235. package/src/views/Users.tsx +6 -18
  236. package/src/views/page-builder/BlockEditor.tsx +3 -6
  237. package/src/views/page-builder/BottomBar.tsx +3 -6
  238. package/src/views/page-builder/NodeSettings.tsx +7 -16
  239. package/src/views/page-builder/PageTemplates.tsx +1 -1
  240. package/src/views/page-builder/SavedSections.tsx +2 -2
  241. package/src/views/page-builder/Widgets.tsx +1 -1
  242. package/src/views/page-editor/SectionInspector.tsx +9 -21
  243. package/src/views/post-editor/PostHeaderPanel.tsx +10 -20
  244. package/src/views/seo/TechnicalTab.tsx +3 -6
  245. package/src/views/settings/components.tsx +19 -26
@@ -1,5 +1,7 @@
1
1
  'use client'
2
2
 
3
+ import { inputVariants } from './Input.js'
4
+
3
5
  export interface SearchInputProps {
4
6
  value: string
5
7
  onChange: (value: string) => void
@@ -10,11 +12,12 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
10
12
  return (
11
13
  <div className="relative flex-1">
12
14
  <svg
13
- className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--muted-foreground)]"
15
+ className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--muted)]"
14
16
  fill="none"
15
17
  viewBox="0 0 24 24"
16
18
  stroke="currentColor"
17
19
  strokeWidth={2}
20
+ aria-hidden
18
21
  >
19
22
  <path
20
23
  strokeLinecap="round"
@@ -27,12 +30,13 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
27
30
  value={value}
28
31
  onChange={(e) => onChange(e.target.value)}
29
32
  placeholder={placeholder}
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)]"
33
+ className={inputVariants({ class: 'py-2 pr-8 pl-9' })}
31
34
  />
32
35
  {value && (
33
36
  <button
37
+ type="button"
34
38
  onClick={() => onChange('')}
35
- className="absolute top-1/2 right-2 -translate-y-1/2 rounded p-0.5 text-[var(--muted-foreground)] hover:text-[var(--foreground)]"
39
+ className="absolute top-1/2 right-2 -translate-y-1/2 rounded p-0.5 text-[var(--muted)] hover:text-[var(--txt)]"
36
40
  aria-label="Clear search"
37
41
  >
38
42
  <svg
@@ -41,12 +45,13 @@ export function SearchInput({ value, onChange, placeholder = 'Search...' }: Sear
41
45
  viewBox="0 0 24 24"
42
46
  stroke="currentColor"
43
47
  strokeWidth={2}
48
+ aria-hidden
44
49
  >
45
50
  <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
46
51
  </svg>
47
52
  </button>
48
53
  )}
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">
54
+ <kbd className="pointer-events-none absolute top-1/2 right-8 hidden -translate-y-1/2 rounded border border-[var(--bdr)] px-1.5 py-0.5 text-[10px] text-[var(--muted)] sm:inline-block">
50
55
  ⌘K
51
56
  </kbd>
52
57
  </div>
@@ -1,25 +1,22 @@
1
1
  import { forwardRef, type SelectHTMLAttributes, type ReactNode } from 'react'
2
2
  import { cv, type VariantProps } from '../../lib/cv.js'
3
+ import { formControlBase, formControlState } from './form-control.js'
3
4
 
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
- },
5
+ const select = cv(`${formControlBase} appearance-none pr-8`, {
6
+ variants: {
7
+ size: {
8
+ sm: 'py-1.5 pr-7 pl-3 text-[length:var(--fs-sub)]',
9
+ md: '',
10
+ lg: 'py-2.5 pr-9 pl-3',
11
+ },
12
+ state: {
13
+ default: formControlState.default,
14
+ invalid: formControlState.invalid,
15
+ valid: formControlState.valid,
19
16
  },
20
- defaultVariants: { size: 'md', state: 'default' },
21
17
  },
22
- )
18
+ defaultVariants: { size: 'md', state: 'default' },
19
+ })
23
20
 
24
21
  export type SelectVariants = VariantProps<typeof select>
25
22
 
@@ -39,7 +36,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select
39
36
  </select>
40
37
  <svg
41
38
  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)]"
39
+ className="pointer-events-none absolute top-1/2 right-2 h-4 w-4 -translate-y-1/2 text-[var(--muted)]"
43
40
  viewBox="0 0 20 20"
44
41
  fill="currentColor"
45
42
  >
@@ -0,0 +1,37 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react'
2
+
3
+ /**
4
+ * Design-system stat pill (`.stat-pill`): a compact card-surface chip with a
5
+ * bold count and a muted label, used in page headers to summarize list
6
+ * contents (e.g. "12 Total · 8 Active · 4 Inactive").
7
+ */
8
+ export interface StatPillProps extends HTMLAttributes<HTMLSpanElement> {
9
+ /** The count/value, rendered bold in `--txt` (override color via valueClassName). */
10
+ value: ReactNode
11
+ /** Muted label shown after the value. */
12
+ label: ReactNode
13
+ /** Optional leading icon. */
14
+ icon?: ReactNode
15
+ /** Extra classes for the value (e.g. `text-[var(--suc)]` for an Active count). */
16
+ valueClassName?: string
17
+ }
18
+
19
+ export function StatPill({
20
+ value,
21
+ label,
22
+ icon,
23
+ valueClassName,
24
+ className,
25
+ ...rest
26
+ }: StatPillProps) {
27
+ return (
28
+ <span
29
+ className={`inline-flex items-center gap-[5px] rounded-[6px] border border-[var(--bdr)] bg-[var(--card)] px-2.5 py-1 text-[length:var(--fs-sub)] text-[var(--sub)] ${className ?? ''}`}
30
+ {...rest}
31
+ >
32
+ {icon}
33
+ <b className={`font-semibold text-[var(--txt)] ${valueClassName ?? ''}`}>{value}</b>
34
+ {label}
35
+ </span>
36
+ )
37
+ }
@@ -0,0 +1,66 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react'
2
+ import { cv, type VariantProps } from '../../lib/cv.js'
3
+
4
+ /**
5
+ * Canonical status chip from the design system (`.status` + `.status-dot`):
6
+ * a rectangular 5px-radius chip with a 5px dot, 11.5px medium text, and
7
+ * tint backgrounds per tone. This is the single source of truth for
8
+ * document / entity statuses across Posts, Pages, Forms, SEO, and tables —
9
+ * do not roll per-view status pills.
10
+ */
11
+ const statusBadge = cv(
12
+ 'inline-flex items-center gap-[5px] rounded-[5px] px-[9px] py-[3px] text-[length:var(--fs-hint)] font-medium whitespace-nowrap',
13
+ {
14
+ variants: {
15
+ tone: {
16
+ /** Published / Active — success tint (`.s-pub`). */
17
+ success: 'bg-[var(--suc-l)] text-[var(--suc)]',
18
+ /** Scheduled / Pending — warning tint (`.s-sch`). */
19
+ warning: 'bg-[var(--warn-l)] text-[var(--warn)]',
20
+ /** Errors / failing states. */
21
+ danger: 'bg-[var(--err-l)] text-[var(--err)]',
22
+ /** Informational states. */
23
+ info: 'bg-[var(--info-l)] text-[var(--info)]',
24
+ /** Draft — page background + border (`.s-dft`). */
25
+ neutral: 'border border-[var(--bdr)] bg-[var(--bg)] text-[var(--sub)]',
26
+ /** Archived — dimmest treatment (`.s-arc`). */
27
+ muted: 'border border-[var(--bdr)] bg-[var(--bg)] text-[var(--muted)]',
28
+ },
29
+ },
30
+ defaultVariants: { tone: 'neutral' },
31
+ },
32
+ )
33
+
34
+ const DOT_CLASS: Record<NonNullable<StatusBadgeVariants['tone']>, string> = {
35
+ success: 'bg-[var(--suc)]',
36
+ warning: 'bg-[var(--warn)]',
37
+ danger: 'bg-[var(--err)]',
38
+ info: 'bg-[var(--info)]',
39
+ neutral: 'bg-[var(--muted)]',
40
+ muted: 'bg-[var(--muted)]',
41
+ }
42
+
43
+ export type StatusBadgeVariants = VariantProps<typeof statusBadge>
44
+ export type StatusBadgeTone = NonNullable<StatusBadgeVariants['tone']>
45
+
46
+ export interface StatusBadgeProps extends HTMLAttributes<HTMLSpanElement>, StatusBadgeVariants {
47
+ /** Hide the leading dot (rare — e.g. numeric chips). */
48
+ dot?: boolean
49
+ children: ReactNode
50
+ }
51
+
52
+ export function StatusBadge({ tone, dot = true, className, children, ...rest }: StatusBadgeProps) {
53
+ return (
54
+ <span className={statusBadge({ tone, class: className })} {...rest}>
55
+ {dot && (
56
+ <span
57
+ aria-hidden
58
+ className={`h-[5px] w-[5px] shrink-0 rounded-full ${DOT_CLASS[tone ?? 'neutral']}`}
59
+ />
60
+ )}
61
+ {children}
62
+ </span>
63
+ )
64
+ }
65
+
66
+ export { statusBadge as statusBadgeVariants }
@@ -0,0 +1,85 @@
1
+ 'use client'
2
+
3
+ import * as Switch from '@radix-ui/react-switch'
4
+ import { cv, type VariantProps } from '../../lib/cv.js'
5
+
6
+ const toggleTrack = cv(
7
+ 'relative inline-flex shrink-0 cursor-pointer rounded-[var(--r-pill)] transition-colors duration-[var(--motion-base)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ /** Design system default — accent when on. */
12
+ accent: 'bg-[var(--bdr)] data-[state=checked]:bg-[var(--acc)]',
13
+ /** Semantic enable/disable — success green when on (tables, script tags). */
14
+ enabled: 'bg-[var(--bdr)] data-[state=checked]:bg-[var(--suc)]',
15
+ },
16
+ size: {
17
+ /** Matches `.tog-track` in the design system handoff (38×21). */
18
+ md: 'h-[21px] w-[38px]',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ variant: 'accent',
23
+ size: 'md',
24
+ },
25
+ },
26
+ )
27
+
28
+ const toggleThumb = cv(
29
+ 'pointer-events-none block rounded-full bg-white shadow-[0_1px_3px_rgba(0,0,0,0.2)] transition-transform duration-[var(--motion-base)]',
30
+ {
31
+ variants: {
32
+ size: {
33
+ md: 'h-4 w-4 translate-x-0.5 data-[state=checked]:translate-x-[17.5px]',
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ size: 'md',
38
+ },
39
+ },
40
+ )
41
+
42
+ export type ToggleVariants = VariantProps<typeof toggleTrack>
43
+
44
+ export interface ToggleProps extends ToggleVariants {
45
+ checked: boolean
46
+ onCheckedChange: (checked: boolean) => void
47
+ disabled?: boolean
48
+ id?: string
49
+ 'aria-label'?: string
50
+ 'aria-labelledby'?: string
51
+ 'aria-describedby'?: string
52
+ }
53
+
54
+ /**
55
+ * Design-system toggle (`.tog-track` / `.tog-thumb`). Use `variant="enabled"`
56
+ * for on/off tables where green communicates active state.
57
+ */
58
+ export function Toggle({
59
+ checked,
60
+ onCheckedChange,
61
+ disabled,
62
+ variant,
63
+ size,
64
+ id,
65
+ 'aria-label': ariaLabel,
66
+ 'aria-labelledby': ariaLabelledBy,
67
+ 'aria-describedby': ariaDescribedBy,
68
+ }: ToggleProps) {
69
+ return (
70
+ <Switch.Root
71
+ id={id}
72
+ checked={checked}
73
+ onCheckedChange={onCheckedChange}
74
+ disabled={disabled}
75
+ aria-label={ariaLabel}
76
+ aria-labelledby={ariaLabelledBy}
77
+ aria-describedby={ariaDescribedBy}
78
+ className={toggleTrack({ variant, size })}
79
+ >
80
+ <Switch.Thumb className={toggleThumb({ size })} />
81
+ </Switch.Root>
82
+ )
83
+ }
84
+
85
+ export { toggleTrack as toggleTrackVariants, toggleThumb as toggleThumbVariants }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared form-control surface styles (`.inp` in the design system handoff).
3
+ * Imported by Input and Select so geometry stays aligned.
4
+ */
5
+ export const formControlBase =
6
+ 'w-full rounded-[var(--r-control)] border border-[var(--bdr)] bg-[var(--bg)] px-3 py-[9px] text-[length:var(--fs-body)] text-[var(--txt)] outline-none transition-[border-color] duration-150 placeholder:text-[var(--muted)] focus:border-[var(--acc-m)] focus-visible:border-[var(--acc-m)] disabled:cursor-not-allowed disabled:opacity-50'
7
+
8
+ export const formControlState = {
9
+ default: '',
10
+ invalid: 'border-[var(--err)] focus:border-[var(--err)] focus-visible:border-[var(--err)]',
11
+ valid: 'border-[var(--suc)] focus:border-[var(--suc)] focus-visible:border-[var(--suc)]',
12
+ } as const
@@ -34,5 +34,14 @@ export { Pagination } from './Pagination.js'
34
34
  export type { PaginationProps } from './Pagination.js'
35
35
  export { SearchInput } from './SearchInput.js'
36
36
  export type { SearchInputProps } from './SearchInput.js'
37
+ export { Toggle, toggleTrackVariants, toggleThumbVariants } from './Toggle.js'
38
+ export type { ToggleProps, ToggleVariants } from './Toggle.js'
39
+ export { StatusBadge, statusBadgeVariants } from './StatusBadge.js'
40
+ export type { StatusBadgeProps, StatusBadgeVariants, StatusBadgeTone } from './StatusBadge.js'
41
+ export { FilterPill } from './FilterPill.js'
42
+ export type { FilterPillProps } from './FilterPill.js'
43
+ export { StatPill } from './StatPill.js'
44
+ export type { StatPillProps } from './StatPill.js'
45
+ export { formControlBase, formControlState } from './form-control.js'
37
46
  export { CommandPalette } from './CommandPalette.js'
38
47
  export type { CommandPaletteProps } from './CommandPalette.js'
@@ -38,6 +38,7 @@ import {
38
38
  safeJsonStringify,
39
39
  switchUnionVariant,
40
40
  } from './component-block-helpers.js'
41
+ import { Toggle } from '../components/ui/Toggle.js'
41
42
 
42
43
  export { defaultForType }
43
44
 
@@ -249,21 +250,11 @@ function BooleanInput({
249
250
  return (
250
251
  <div className="flex items-center justify-between text-sm">
251
252
  <span>{label}</span>
252
- <button
253
- type="button"
254
- role="switch"
255
- aria-checked={boolVal}
256
- onClick={() => onChange(!boolVal)}
257
- className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${
258
- boolVal ? 'bg-[var(--primary)]' : 'bg-[var(--muted)]'
259
- }`}
260
- >
261
- <span
262
- className={`bg-card pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform ${
263
- boolVal ? 'translate-x-5' : 'translate-x-0'
264
- }`}
265
- />
266
- </button>
253
+ <Toggle
254
+ checked={boolVal}
255
+ onCheckedChange={(v) => onChange(v)}
256
+ aria-label={typeof label === 'string' ? label : undefined}
257
+ />
267
258
  </div>
268
259
  )
269
260
  }
@@ -1,5 +1,7 @@
1
1
  'use client'
2
2
 
3
+ import { Toggle } from '../components/ui/Toggle.js'
4
+
3
5
  export interface ToggleFieldProps {
4
6
  label: string
5
7
  value?: boolean
@@ -14,21 +16,7 @@ export function ToggleField({ label, value = false, onChange, helpText }: Toggle
14
16
  <span className="text-sm font-medium">{label}</span>
15
17
  {helpText && <p className="text-xs text-[var(--muted-foreground)]">{helpText}</p>}
16
18
  </div>
17
- <button
18
- type="button"
19
- role="switch"
20
- aria-checked={value}
21
- onClick={() => onChange(!value)}
22
- className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${
23
- value ? 'bg-[var(--primary)]' : 'bg-[var(--muted)]'
24
- }`}
25
- >
26
- <span
27
- className={`bg-card pointer-events-none inline-block h-5 w-5 rounded-full shadow-sm ring-0 transition-transform ${
28
- value ? 'translate-x-5' : 'translate-x-0'
29
- }`}
30
- />
31
- </button>
19
+ <Toggle checked={value} onCheckedChange={onChange} aria-label={label} />
32
20
  </div>
33
21
  )
34
22
  }
@@ -8,6 +8,7 @@ import { CommandPalette } from '../components/CommandPalette.js'
8
8
  import { NotificationBell } from '../components/NotificationBell.js'
9
9
  import { useTheme } from '../components/ThemeProvider.js'
10
10
  import { LocaleSwitcher } from '../components/LocaleSwitcher.js'
11
+ import { inputVariants } from '../components/ui/Input.js'
11
12
 
12
13
  export interface HeaderProps {
13
14
  onToggleSidebar: () => void
@@ -51,7 +52,7 @@ export function Header({
51
52
 
52
53
  return (
53
54
  <>
54
- <header className="border-border bg-background flex h-14 items-center gap-3 border-b px-4">
55
+ <header className="border-sidebar-border bg-sidebar flex h-[var(--tbh)] items-center gap-3 border-b px-4">
55
56
  <button
56
57
  onClick={onToggleSidebar}
57
58
  className="hover:bg-accent rounded-lg p-2 transition-colors md:hidden"
@@ -77,7 +78,10 @@ export function Header({
77
78
  onKeyDown={(e) => {
78
79
  if (e.key === 'Escape') (e.target as HTMLInputElement).blur()
79
80
  }}
80
- className="border-border bg-input-background text-foreground placeholder:text-muted-foreground focus:ring-ring h-9 w-full max-w-md rounded-lg border py-1.5 pr-14 pl-9 text-sm focus:border-transparent focus:ring-2 focus:outline-none"
81
+ className={inputVariants({
82
+ class:
83
+ 'h-9 w-full max-w-md py-1.5 pr-14 pl-9 focus-visible:ring-0 focus-visible:ring-offset-0',
84
+ })}
81
85
  />
82
86
  {/* Keyboard hint pill. Hidden on narrow widths to avoid overlap. */}
83
87
  <span
@@ -253,13 +253,13 @@ export function Sidebar({
253
253
 
254
254
  return (
255
255
  <aside
256
- className={`bg-sidebar border-sidebar-border flex h-full flex-col border-r transition-all duration-200 ${
257
- collapsed ? 'w-20' : 'w-64'
256
+ className={`bg-sidebar border-sidebar-border flex h-full flex-col border-r transition-[width] duration-[var(--motion-layout)] ${
257
+ collapsed ? 'w-[var(--sbw-col)]' : 'w-[var(--sbw)]'
258
258
  }`}
259
259
  >
260
260
  {/* Brand header — no toggle button here anymore; it now lives in the footer. */}
261
261
  <div
262
- className={`border-sidebar-border flex h-14 shrink-0 items-center border-b px-4 ${
262
+ className={`border-sidebar-border flex h-[var(--tbh)] shrink-0 items-center border-b px-4 ${
263
263
  collapsed ? 'justify-center' : ''
264
264
  }`}
265
265
  >
@@ -78,11 +78,7 @@ export function PageContainer({
78
78
  <header className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
79
79
  {(title || description) && (
80
80
  <div className="min-w-0">
81
- {title && (
82
- <h1 className="text-foreground truncate text-2xl font-medium tracking-tight">
83
- {title}
84
- </h1>
85
- )}
81
+ {title && <h1 className="text-foreground truncate">{title}</h1>}
86
82
  {description && <p className="text-muted-foreground mt-1 text-sm">{description}</p>}
87
83
  </div>
88
84
  )}
@@ -49,13 +49,20 @@ export const tokens = {
49
49
 
50
50
  radius: {
51
51
  none: 0,
52
- sm: 2,
53
- md: 6,
54
- lg: 8,
52
+ sm: 6,
53
+ md: 8,
54
+ lg: 10,
55
55
  xl: 12,
56
- full: 9999,
56
+ full: 99,
57
57
  } as const satisfies Record<RadiusToken, number>,
58
58
 
59
+ /** Layout dimensions from the design system (px). */
60
+ layout: {
61
+ sidebarWidth: 224,
62
+ sidebarWidthCollapsed: 60,
63
+ topbarHeight: 56,
64
+ } as const,
65
+
59
66
  // Breakpoints mirror Tailwind defaults so primitives can compose with
60
67
  // utility classes without surprises. Use `matchMedia(`(min-width: …`)` to
61
68
  // sync JS-driven layout decisions with these values.
@@ -0,0 +1,97 @@
1
+ /*
2
+ * ActuateCMS design tokens — scoped to the admin root.
3
+ * Source: design-system/tokens.json (v1.0).
4
+ * Appearance accent overrides set --acc / --primary at runtime.
5
+ */
6
+
7
+ .actuate-admin {
8
+ /* Surfaces */
9
+ --bg: #f5f4fa;
10
+ --sb: #ffffff;
11
+ --card: #ffffff;
12
+ --bdr: #e8e6f2;
13
+
14
+ /* Text */
15
+ --txt: #1a1827;
16
+ --sub: #6b6878;
17
+ --muted: #b0adbe;
18
+
19
+ /* Accent */
20
+ --acc: #7c3aed;
21
+ --acc-l: #ede9fe;
22
+ --acc-m: #c4b5fd;
23
+
24
+ /* Semantic */
25
+ --suc: #059669;
26
+ --suc-l: #ecfdf5;
27
+ --warn: #d97706;
28
+ --warn-l: #fffbeb;
29
+ --err: #dc2626;
30
+ --err-l: #fef2f2;
31
+ --info: #0891b2;
32
+ --info-l: #e0f7fa;
33
+
34
+ /* AI */
35
+ --ai1: #7c3aed;
36
+ --ai2: #c026d3;
37
+ --ai-soft: linear-gradient(135deg, rgba(124, 58, 237, 0.09), rgba(192, 38, 211, 0.07));
38
+
39
+ /* Layout */
40
+ --sbw: 224px;
41
+ --sbw-col: 60px;
42
+ --tbh: 56px;
43
+ --r: 10px;
44
+ --r-control: 8px;
45
+ --r-sm: 6px;
46
+ --r-pill: 99px;
47
+
48
+ /* Elevation */
49
+ --shd: 0 1px 2px rgba(0, 0, 0, 0.05), 0 1px 4px rgba(0, 0, 0, 0.04);
50
+ --shd2: 0 4px 16px rgba(0, 0, 0, 0.1);
51
+
52
+ /* Typography */
53
+ --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
54
+ --font-mono: 'DM Mono', ui-monospace, 'SFMono-Regular', monospace;
55
+ --fs-page: 21px;
56
+ --fs-card: 14px;
57
+ --fs-body: 13px;
58
+ --fs-sub: 12px;
59
+ --fs-hint: 11.5px;
60
+ --fs-label: 10px;
61
+
62
+ /* Motion */
63
+ --motion-fast: 0.12s;
64
+ --motion-base: 0.18s;
65
+ --motion-layout: 0.22s cubic-bezier(0.4, 0, 0.2, 1);
66
+ }
67
+
68
+ .dark .actuate-admin,
69
+ .actuate-admin.dark,
70
+ .actuate-admin[data-theme='dark'] {
71
+ --bg: #0d0c18;
72
+ --sb: #141222;
73
+ --card: #1c1a2e;
74
+ --bdr: #2c2a42;
75
+
76
+ --txt: #eae8f5;
77
+ --sub: #8e8ba8;
78
+ --muted: #504e68;
79
+
80
+ --acc: #9d71f0;
81
+ --acc-l: #231950;
82
+ --acc-m: #5e3eaa;
83
+
84
+ --suc: #10b981;
85
+ --suc-l: #022c22;
86
+ --warn: #fbbf24;
87
+ --warn-l: #1a1000;
88
+ --err: #f87171;
89
+ --err-l: #200808;
90
+ --info: #38bdf8;
91
+ --info-l: #0a2838;
92
+
93
+ --ai-soft: linear-gradient(135deg, rgba(157, 113, 240, 0.16), rgba(192, 38, 211, 0.12));
94
+
95
+ --shd: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2);
96
+ --shd2: 0 4px 20px rgba(0, 0, 0, 0.45);
97
+ }