@actuate-media/cms-admin 0.72.2 → 0.73.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 (71) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/__tests__/components/resizable-aside.test.d.ts +2 -0
  3. package/dist/__tests__/components/resizable-aside.test.d.ts.map +1 -0
  4. package/dist/__tests__/components/resizable-aside.test.js +77 -0
  5. package/dist/__tests__/components/resizable-aside.test.js.map +1 -0
  6. package/dist/__tests__/components/toggle.test.d.ts +2 -0
  7. package/dist/__tests__/components/toggle.test.d.ts.map +1 -0
  8. package/dist/__tests__/components/toggle.test.js +17 -0
  9. package/dist/__tests__/components/toggle.test.js.map +1 -0
  10. package/dist/__tests__/views/post-fields-modal.render.test.js +38 -0
  11. package/dist/__tests__/views/post-fields-modal.render.test.js.map +1 -1
  12. package/dist/actuate-admin.css +1 -1
  13. package/dist/components/MediaPickerModal.d.ts.map +1 -1
  14. package/dist/components/MediaPickerModal.js +1 -1
  15. package/dist/components/MediaPickerModal.js.map +1 -1
  16. package/dist/components/TagInput.js +1 -1
  17. package/dist/components/TagInput.js.map +1 -1
  18. package/dist/components/ui/Modal.d.ts +17 -1
  19. package/dist/components/ui/Modal.d.ts.map +1 -1
  20. package/dist/components/ui/Modal.js +29 -3
  21. package/dist/components/ui/Modal.js.map +1 -1
  22. package/dist/components/ui/ResizableAside.d.ts +38 -0
  23. package/dist/components/ui/ResizableAside.d.ts.map +1 -0
  24. package/dist/components/ui/ResizableAside.js +209 -0
  25. package/dist/components/ui/ResizableAside.js.map +1 -0
  26. package/dist/components/ui/Toggle.d.ts.map +1 -1
  27. package/dist/components/ui/Toggle.js +4 -3
  28. package/dist/components/ui/Toggle.js.map +1 -1
  29. package/dist/fields/RichTextField.d.ts +3 -1
  30. package/dist/fields/RichTextField.d.ts.map +1 -1
  31. package/dist/fields/RichTextField.js +2 -2
  32. package/dist/fields/RichTextField.js.map +1 -1
  33. package/dist/views/Dashboard.d.ts.map +1 -1
  34. package/dist/views/Dashboard.js +1 -1
  35. package/dist/views/Dashboard.js.map +1 -1
  36. package/dist/views/page-editor/PageSectionEditor.js +1 -1
  37. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  38. package/dist/views/page-editor/SectionInspector.d.ts +4 -1
  39. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  40. package/dist/views/page-editor/SectionInspector.js +14 -3
  41. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  42. package/dist/views/post-editor/PostFieldsModal.d.ts +1 -1
  43. package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -1
  44. package/dist/views/post-editor/PostFieldsModal.js +152 -13
  45. package/dist/views/post-editor/PostFieldsModal.js.map +1 -1
  46. package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -1
  47. package/dist/views/post-editor/PostHeaderPanel.js +2 -1
  48. package/dist/views/post-editor/PostHeaderPanel.js.map +1 -1
  49. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  50. package/dist/views/post-editor/PostSectionEditor.js +6 -1
  51. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  52. package/dist/views/post-editor/PostTemplateEditor.js +1 -1
  53. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/__tests__/components/resizable-aside.test.tsx +130 -0
  56. package/src/__tests__/components/toggle.test.tsx +17 -0
  57. package/src/__tests__/views/post-fields-modal.render.test.tsx +54 -0
  58. package/src/components/MediaPickerModal.tsx +6 -2
  59. package/src/components/TagInput.tsx +1 -1
  60. package/src/components/ui/Modal.tsx +46 -3
  61. package/src/components/ui/ResizableAside.tsx +317 -0
  62. package/src/components/ui/Toggle.tsx +4 -3
  63. package/src/fields/RichTextField.tsx +4 -1
  64. package/src/styles/actuate-tokens.css +7 -2
  65. package/src/views/Dashboard.tsx +2 -1
  66. package/src/views/page-editor/PageSectionEditor.tsx +1 -1
  67. package/src/views/page-editor/SectionInspector.tsx +54 -26
  68. package/src/views/post-editor/PostFieldsModal.tsx +412 -174
  69. package/src/views/post-editor/PostHeaderPanel.tsx +15 -11
  70. package/src/views/post-editor/PostSectionEditor.tsx +9 -1
  71. package/src/views/post-editor/PostTemplateEditor.tsx +1 -1
@@ -8,6 +8,8 @@ export interface RichTextFieldProps {
8
8
  onChange: (value: string) => void
9
9
  required?: boolean
10
10
  helpText?: string
11
+ /** Passed through to the underlying editor shell (e.g. max-height). */
12
+ className?: string
11
13
  }
12
14
 
13
15
  export function RichTextField({
@@ -16,6 +18,7 @@ export function RichTextField({
16
18
  onChange,
17
19
  required,
18
20
  helpText,
21
+ className,
19
22
  }: RichTextFieldProps) {
20
23
  return (
21
24
  <div>
@@ -23,7 +26,7 @@ export function RichTextField({
23
26
  {label}
24
27
  {required && <span className="ml-0.5 text-[var(--destructive)]">*</span>}
25
28
  </label>
26
- <TipTapEditor content={value} onChange={onChange} />
29
+ <TipTapEditor content={value} onChange={onChange} className={className} />
27
30
  {helpText && <p className="mt-1 text-xs text-[var(--muted-foreground)]">{helpText}</p>}
28
31
  </div>
29
32
  )
@@ -14,7 +14,11 @@
14
14
  /* Text */
15
15
  --txt: #1a1827;
16
16
  --sub: #6b6878;
17
- --muted: #b0adbe;
17
+ /* Deviates from tokens.json's #B0ADBE (2.2:1 on white — illegible). The
18
+ admin uses --muted as real text (section labels, hints, icons), so it
19
+ must meet WCAG AA (>=4.5:1) on --card and --bg while staying one step
20
+ lighter than --sub to preserve the text hierarchy. */
21
+ --muted: #716e83;
18
22
 
19
23
  /* Accent */
20
24
  --acc: #7c3aed;
@@ -79,7 +83,8 @@
79
83
 
80
84
  --txt: #eae8f5;
81
85
  --sub: #8e8ba8;
82
- --muted: #504e68;
86
+ /* Same AA deviation as light mode (tokens.json #504E68 is 2.1:1 on card). */
87
+ --muted: #8784a2;
83
88
 
84
89
  --acc: #9d71f0;
85
90
  --acc-l: #231950;
@@ -585,7 +585,8 @@ export function Dashboard({ config, session, onNavigate }: DashboardProps) {
585
585
  <h1 className="text-foreground">
586
586
  {greeting}, {userName} <span aria-hidden>👋</span>
587
587
  </h1>
588
- <p className="text-muted-foreground text-sm">
588
+ {/* data-volatile: the literal date changes daily — visual tests mask it. */}
589
+ <p className="text-muted-foreground text-sm" data-volatile>
589
590
  {dateStr}
590
591
  {totalIssues > 0 && (
591
592
  <span className="hidden sm:inline">
@@ -545,7 +545,7 @@ export function PageSectionEditor({
545
545
  />
546
546
  )}
547
547
 
548
- <div className="flex flex-1 overflow-hidden">
548
+ <div className="relative flex flex-1 overflow-hidden">
549
549
  <SectionsPanel
550
550
  sections={page.sections}
551
551
  selectedId={selectedId}
@@ -19,7 +19,9 @@ import {
19
19
  } from '@dnd-kit/sortable'
20
20
  import { CSS } from '@dnd-kit/utilities'
21
21
  import { MediaPickerModal } from '../../components/MediaPickerModal.js'
22
+ import { InspectorExpandButton, ResizableAside } from '../../components/ui/ResizableAside.js'
22
23
  import { Toggle } from '../../components/ui/Toggle.js'
24
+ import { RichTextField } from '../../fields/RichTextField.js'
23
25
  import type { PageSection } from '../../lib/page-editor-service.js'
24
26
  import type { ResolvedRepeater, SectionSettings } from './section-types.js'
25
27
  import {
@@ -37,11 +39,37 @@ interface SectionInspectorProps {
37
39
  onSettingsChange: (patch: Partial<SectionSettings>) => void
38
40
  onMetaChange: (patch: { name?: string; internalLabel?: string }) => void
39
41
  onToggleVisibility: () => void
42
+ /** Post body field — shown when editing an Article Body section bound to the post field. */
43
+ postBody?: string
44
+ onPostBodyChange?: (html: string) => void
40
45
  }
41
46
 
42
47
  const LABEL = 'text-foreground mb-1 block text-xs font-medium'
43
48
  const INPUT =
44
49
  'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'
50
+ const INSPECTOR_SHELL = 'bg-card border-border flex h-full flex-col border-l'
51
+
52
+ function InspectorHeaderActions({
53
+ onClose,
54
+ closeLabel = 'Close inspector',
55
+ }: {
56
+ onClose: () => void
57
+ closeLabel?: string
58
+ }) {
59
+ return (
60
+ <div className="flex shrink-0 items-center gap-0.5">
61
+ <InspectorExpandButton />
62
+ <button
63
+ type="button"
64
+ onClick={onClose}
65
+ aria-label={closeLabel}
66
+ className="text-muted-foreground hover:text-foreground hover:bg-accent flex h-9 w-9 items-center justify-center rounded-md md:h-7 md:w-7"
67
+ >
68
+ <X className="h-4 w-4" aria-hidden />
69
+ </button>
70
+ </div>
71
+ )
72
+ }
45
73
 
46
74
  export function SectionInspector({
47
75
  section,
@@ -51,8 +79,17 @@ export function SectionInspector({
51
79
  onSettingsChange,
52
80
  onMetaChange,
53
81
  onToggleVisibility,
82
+ postBody,
83
+ onPostBodyChange,
54
84
  }: SectionInspectorProps) {
55
85
  const def = getSectionType(section.sectionType)
86
+ const bodySource = String(section.content.source ?? 'field')
87
+ const editPostBodyField =
88
+ section.sectionType === 'article-body' &&
89
+ bodySource === 'field' &&
90
+ onPostBodyChange !== undefined
91
+ const contentFields =
92
+ def?.fields.filter((field) => !(editPostBodyField && field.key === 'body')) ?? []
56
93
 
57
94
  // Escape closes the inspector.
58
95
  useEffect(() => {
@@ -67,24 +104,17 @@ export function SectionInspector({
67
104
  // editable controls, so the externally-managed content is never altered here.
68
105
  if (section.unmanaged) {
69
106
  return (
70
- <aside
107
+ <ResizableAside
71
108
  role="dialog"
72
109
  aria-label={`${section.name} (read-only)`}
73
- className="bg-card border-border flex h-full w-80 shrink-0 flex-col border-l"
110
+ className={INSPECTOR_SHELL}
74
111
  >
75
112
  <div className="border-border flex items-center justify-between border-b px-4 py-3">
76
113
  <div className="min-w-0">
77
114
  <h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
78
115
  <p className="text-muted-foreground text-xs">Managed outside the editor</p>
79
116
  </div>
80
- <button
81
- type="button"
82
- onClick={onClose}
83
- aria-label="Close inspector"
84
- className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
85
- >
86
- <X className="h-4 w-4" aria-hidden />
87
- </button>
117
+ <InspectorHeaderActions onClose={onClose} />
88
118
  </div>
89
119
  <div className="flex-1 space-y-3 overflow-y-auto p-4">
90
120
  <div className="border-border bg-muted text-muted-foreground space-y-2 rounded-md border p-3 text-sm">
@@ -106,29 +136,18 @@ export function SectionInspector({
106
136
  </p>
107
137
  </div>
108
138
  </div>
109
- </aside>
139
+ </ResizableAside>
110
140
  )
111
141
  }
112
142
 
113
143
  return (
114
- <aside
115
- role="dialog"
116
- aria-label={`Edit ${section.name}`}
117
- className="bg-card border-border flex h-full w-80 shrink-0 flex-col border-l"
118
- >
144
+ <ResizableAside role="dialog" aria-label={`Edit ${section.name}`} className={INSPECTOR_SHELL}>
119
145
  <div className="border-border flex items-center justify-between border-b px-4 py-3">
120
146
  <div className="min-w-0">
121
147
  <h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
122
148
  <p className="text-muted-foreground text-xs">{def?.name ?? 'Section'}</p>
123
149
  </div>
124
- <button
125
- type="button"
126
- onClick={onClose}
127
- aria-label="Close inspector"
128
- className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
129
- >
130
- <X className="h-4 w-4" aria-hidden />
131
- </button>
150
+ <InspectorHeaderActions onClose={onClose} />
132
151
  </div>
133
152
 
134
153
  <div className="flex-1 space-y-6 overflow-y-auto p-4">
@@ -143,7 +162,16 @@ export function SectionInspector({
143
162
  <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
144
163
  Content
145
164
  </legend>
146
- {def?.fields.map((field) => (
165
+ {editPostBodyField && (
166
+ <RichTextField
167
+ label="Body"
168
+ value={postBody ?? ''}
169
+ onChange={onPostBodyChange}
170
+ className="[&_.ProseMirror]:min-h-[240px]"
171
+ helpText="Main article copy — rendered in the canvas preview and on the live site."
172
+ />
173
+ )}
174
+ {contentFields.map((field) => (
147
175
  <ContentField
148
176
  key={field.key}
149
177
  field={field}
@@ -294,7 +322,7 @@ export function SectionInspector({
294
322
  </div>
295
323
  </fieldset>
296
324
  </div>
297
- </aside>
325
+ </ResizableAside>
298
326
  )
299
327
  }
300
328