@actuate-media/cms-admin 0.38.1 → 0.40.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.
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +25 -0
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breadcrumbs-is-id.test.d.ts +2 -0
- package/dist/__tests__/components/breadcrumbs-is-id.test.d.ts.map +1 -0
- package/dist/__tests__/components/breadcrumbs-is-id.test.js +28 -0
- package/dist/__tests__/components/breadcrumbs-is-id.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service-api.test.js +40 -0
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/views/appearance-settings.render.test.js +34 -0
- package/dist/__tests__/views/appearance-settings.render.test.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/Breadcrumbs.d.ts +1 -0
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +10 -2
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/components/MediaPickerModal.d.ts +9 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.js +11 -5
- package/dist/components/MediaPickerModal.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +10 -3
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +5 -2
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +65 -27
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +7 -4
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/section-types.d.ts +2 -2
- package/dist/views/page-editor/section-types.d.ts.map +1 -1
- package/dist/views/page-editor/section-types.js +1 -1
- package/dist/views/page-editor/section-types.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +14 -1
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/settings/BrandingCard.d.ts.map +1 -1
- package/dist/views/settings/BrandingCard.js +15 -4
- package/dist/views/settings/BrandingCard.js.map +1 -1
- package/dist/views/settings/useAppearanceSettings.d.ts +10 -0
- package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
- package/dist/views/settings/useAppearanceSettings.js +24 -0
- package/dist/views/settings/useAppearanceSettings.js.map +1 -1
- package/package.json +2 -2
- package/src/AdminRoot.tsx +31 -0
- package/src/__tests__/components/breadcrumbs-is-id.test.ts +32 -0
- package/src/__tests__/lib/page-editor-service-api.test.ts +43 -0
- package/src/__tests__/views/appearance-settings.render.test.tsx +45 -0
- package/src/components/Breadcrumbs.tsx +11 -2
- package/src/components/MediaPickerModal.tsx +68 -39
- package/src/lib/page-editor-service.ts +10 -3
- package/src/views/page-editor/PageSectionEditor.tsx +24 -1
- package/src/views/page-editor/SectionInspector.tsx +235 -121
- package/src/views/page-editor/SectionsPanel.tsx +54 -38
- package/src/views/page-editor/section-types.ts +5 -0
- package/src/views/page-editor/sections/SectionRenderer.tsx +25 -1
- package/src/views/settings/BrandingCard.tsx +37 -2
- package/src/views/settings/useAppearanceSettings.ts +39 -0
|
@@ -5,14 +5,20 @@ import {
|
|
|
5
5
|
ChevronDown,
|
|
6
6
|
ChevronRight,
|
|
7
7
|
ImageIcon,
|
|
8
|
+
Library,
|
|
8
9
|
Loader2,
|
|
9
10
|
Trash2,
|
|
10
11
|
Upload,
|
|
11
12
|
} from 'lucide-react'
|
|
12
|
-
import { useRef } from 'react'
|
|
13
|
+
import { useRef, useState } from 'react'
|
|
13
14
|
|
|
15
|
+
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
|
|
14
16
|
import { SettingsCard, SettingsControlRow, SettingsSubsection } from './components.js'
|
|
15
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
BrandAssetField,
|
|
19
|
+
SelectedBrandAsset,
|
|
20
|
+
UseAppearanceSettings,
|
|
21
|
+
} from './useAppearanceSettings.js'
|
|
16
22
|
|
|
17
23
|
const LOGO_ACCEPT = 'image/svg+xml,image/png'
|
|
18
24
|
|
|
@@ -33,6 +39,7 @@ function LogoField({
|
|
|
33
39
|
canEdit,
|
|
34
40
|
warning,
|
|
35
41
|
onUpload,
|
|
42
|
+
onSelectFromLibrary,
|
|
36
43
|
onRemove,
|
|
37
44
|
}: {
|
|
38
45
|
label: string
|
|
@@ -47,9 +54,11 @@ function LogoField({
|
|
|
47
54
|
canEdit: boolean
|
|
48
55
|
warning?: string
|
|
49
56
|
onUpload: (field: BrandAssetField, file: File) => void
|
|
57
|
+
onSelectFromLibrary: (field: BrandAssetField, item: SelectedBrandAsset) => void
|
|
50
58
|
onRemove: (field: BrandAssetField) => void
|
|
51
59
|
}) {
|
|
52
60
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
61
|
+
const [pickerOpen, setPickerOpen] = useState(false)
|
|
53
62
|
const assetId = brand[field]
|
|
54
63
|
const asset = assetId ? assets[assetId] : undefined
|
|
55
64
|
const uploading = uploadingField === field
|
|
@@ -102,6 +111,15 @@ function LogoField({
|
|
|
102
111
|
<Trash2 size={14} aria-hidden="true" /> Remove
|
|
103
112
|
</button>
|
|
104
113
|
)}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => setPickerOpen(true)}
|
|
117
|
+
disabled={!canEdit || uploading}
|
|
118
|
+
aria-label={`Choose ${label.toLowerCase()} from media library`}
|
|
119
|
+
className="border-border text-foreground hover:bg-accent inline-flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
120
|
+
>
|
|
121
|
+
<Library size={14} aria-hidden="true" /> Media library
|
|
122
|
+
</button>
|
|
105
123
|
<button
|
|
106
124
|
type="button"
|
|
107
125
|
onClick={() => inputRef.current?.click()}
|
|
@@ -116,6 +134,22 @@ function LogoField({
|
|
|
116
134
|
)}
|
|
117
135
|
{asset ? 'Replace' : 'Upload'}
|
|
118
136
|
</button>
|
|
137
|
+
<MediaPickerModal
|
|
138
|
+
open={pickerOpen}
|
|
139
|
+
onClose={() => setPickerOpen(false)}
|
|
140
|
+
accept={accept}
|
|
141
|
+
libraryOnly
|
|
142
|
+
onSelect={() => {}}
|
|
143
|
+
onSelectItem={(item) => {
|
|
144
|
+
onSelectFromLibrary(field, {
|
|
145
|
+
id: item.id,
|
|
146
|
+
url: item.url,
|
|
147
|
+
mimeType: item.mimeType,
|
|
148
|
+
width: item.width,
|
|
149
|
+
height: item.height,
|
|
150
|
+
})
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
119
153
|
</>
|
|
120
154
|
)
|
|
121
155
|
|
|
@@ -197,6 +231,7 @@ export function BrandingCard({ s, canEdit }: { s: UseAppearanceSettings; canEdit
|
|
|
197
231
|
uploadingField: s.uploadingField,
|
|
198
232
|
canEdit,
|
|
199
233
|
onUpload: s.uploadAsset,
|
|
234
|
+
onSelectFromLibrary: s.selectAsset,
|
|
200
235
|
onRemove: s.removeAsset,
|
|
201
236
|
}
|
|
202
237
|
|
|
@@ -75,6 +75,15 @@ export interface AssetPreview {
|
|
|
75
75
|
shape: LogoShape
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/** An existing media-library asset chosen for a brand field via the picker. */
|
|
79
|
+
export interface SelectedBrandAsset {
|
|
80
|
+
id: string
|
|
81
|
+
url: string
|
|
82
|
+
mimeType: string
|
|
83
|
+
width?: number
|
|
84
|
+
height?: number
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
const LOGO_MIME = ['image/svg+xml', 'image/png']
|
|
79
88
|
const FAVICON_MIME = ['image/svg+xml', 'image/png']
|
|
80
89
|
const MAX_BYTES = 1024 * 1024 // 1 MB
|
|
@@ -103,6 +112,8 @@ export interface UseAppearanceSettings {
|
|
|
103
112
|
setBrandingMode: (v: AdminBrandingMode) => void
|
|
104
113
|
setAltText: (v: string) => void
|
|
105
114
|
uploadAsset: (field: BrandAssetField, file: File) => Promise<void>
|
|
115
|
+
/** Point a brand field at an EXISTING media asset (no re-upload, no duplicate). */
|
|
116
|
+
selectAsset: (field: BrandAssetField, item: SelectedBrandAsset) => void
|
|
106
117
|
removeAsset: (field: BrandAssetField) => void
|
|
107
118
|
save: () => Promise<void>
|
|
108
119
|
reset: () => void
|
|
@@ -375,6 +386,33 @@ export function useAppearanceSettings(
|
|
|
375
386
|
[appearance, brand, assets, update],
|
|
376
387
|
)
|
|
377
388
|
|
|
389
|
+
const selectAsset = useCallback(
|
|
390
|
+
(field: BrandAssetField, item: SelectedBrandAsset) => {
|
|
391
|
+
// Same allow-list as upload: brand assets must be SVG or PNG, regardless
|
|
392
|
+
// of what else lives in the media library.
|
|
393
|
+
const isFavicon = field === 'faviconSourceAssetId'
|
|
394
|
+
const allowed = isFavicon ? FAVICON_MIME : LOGO_MIME
|
|
395
|
+
if (!allowed.includes(item.mimeType)) {
|
|
396
|
+
setSaveError(`Unsupported file type. Brand assets must be ${allowed.join(' or ')}.`)
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
setSaveError(null)
|
|
400
|
+
const nextAssets: Record<string, AssetPreview> = {
|
|
401
|
+
...assets,
|
|
402
|
+
[item.id]: {
|
|
403
|
+
id: item.id,
|
|
404
|
+
url: item.url,
|
|
405
|
+
mimeType: item.mimeType,
|
|
406
|
+
width: item.width,
|
|
407
|
+
height: item.height,
|
|
408
|
+
shape: classifyLogoShape(item.width, item.height),
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
update(appearance, { ...brand, [field]: item.id }, nextAssets)
|
|
412
|
+
},
|
|
413
|
+
[appearance, brand, assets, update],
|
|
414
|
+
)
|
|
415
|
+
|
|
378
416
|
const removeAsset = useCallback(
|
|
379
417
|
(field: BrandAssetField) => {
|
|
380
418
|
const next = { ...brand }
|
|
@@ -505,6 +543,7 @@ export function useAppearanceSettings(
|
|
|
505
543
|
setBrandingMode,
|
|
506
544
|
setAltText,
|
|
507
545
|
uploadAsset,
|
|
546
|
+
selectAsset,
|
|
508
547
|
removeAsset,
|
|
509
548
|
save,
|
|
510
549
|
reset,
|