@prenta/admin 0.98.1 → 1.0.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/CHANGELOG.md +6 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +17 -28
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/seo-score-bands.test.js +0 -3
- package/dist/__tests__/components/seo-score-bands.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-posts.test.js +6 -11
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-submenu.render.test.js +3 -2
- package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
- package/dist/__tests__/lib/page-editor-service-api.test.js +26 -23
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +0 -1
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.js +0 -1
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +3 -4
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +7 -38
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +0 -7
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +9 -9
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/PageEditor.d.ts +2 -2
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +9 -15
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/package.json +4 -4
- package/src/AdminRoot.tsx +18 -68
- package/src/__tests__/components/seo-score-bands.test.ts +0 -3
- package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
- package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
- package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
- package/src/__tests__/views/page-section-editor.test.tsx +0 -1
- package/src/__tests__/views/page-tab.render.test.tsx +0 -1
- package/src/index.ts +1 -11
- package/src/layout/Sidebar.tsx +6 -41
- package/src/lib/page-editor-service.ts +9 -16
- package/src/views/PageEditor.tsx +2 -2
- package/src/views/page-editor/PageSectionEditor.tsx +8 -37
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
- package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js +0 -65
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.js +0 -126
- package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
- package/dist/hooks/useBuilderState.d.ts +0 -49
- package/dist/hooks/useBuilderState.d.ts.map +0 -1
- package/dist/hooks/useBuilderState.js +0 -238
- package/dist/hooks/useBuilderState.js.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.js +0 -40
- package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.js +0 -173
- package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
- package/dist/views/page-builder/BlockEditor.d.ts +0 -11
- package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
- package/dist/views/page-builder/BlockEditor.js +0 -159
- package/dist/views/page-builder/BlockEditor.js.map +0 -1
- package/dist/views/page-builder/BlockPicker.d.ts +0 -7
- package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
- package/dist/views/page-builder/BlockPicker.js +0 -103
- package/dist/views/page-builder/BlockPicker.js.map +0 -1
- package/dist/views/page-builder/BottomBar.d.ts +0 -9
- package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
- package/dist/views/page-builder/BottomBar.js +0 -13
- package/dist/views/page-builder/BottomBar.js.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
- package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.js +0 -18
- package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
- package/dist/views/page-builder/ContextPanel.d.ts +0 -20
- package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
- package/dist/views/page-builder/ContextPanel.js +0 -43
- package/dist/views/page-builder/ContextPanel.js.map +0 -1
- package/dist/views/page-builder/DesignScore.d.ts +0 -6
- package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
- package/dist/views/page-builder/DesignScore.js +0 -93
- package/dist/views/page-builder/DesignScore.js.map +0 -1
- package/dist/views/page-builder/NodeSettings.d.ts +0 -12
- package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
- package/dist/views/page-builder/NodeSettings.js +0 -80
- package/dist/views/page-builder/NodeSettings.js.map +0 -1
- package/dist/views/page-builder/PageBuilder.d.ts +0 -10
- package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
- package/dist/views/page-builder/PageBuilder.js +0 -222
- package/dist/views/page-builder/PageBuilder.js.map +0 -1
- package/dist/views/page-builder/PageSettings.d.ts +0 -7
- package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
- package/dist/views/page-builder/PageSettings.js +0 -27
- package/dist/views/page-builder/PageSettings.js.map +0 -1
- package/dist/views/page-builder/PageTemplates.d.ts +0 -5
- package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
- package/dist/views/page-builder/PageTemplates.js +0 -146
- package/dist/views/page-builder/PageTemplates.js.map +0 -1
- package/dist/views/page-builder/RelationshipField.d.ts +0 -15
- package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
- package/dist/views/page-builder/RelationshipField.js +0 -65
- package/dist/views/page-builder/RelationshipField.js.map +0 -1
- package/dist/views/page-builder/SEOPanel.d.ts +0 -9
- package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
- package/dist/views/page-builder/SEOPanel.js +0 -38
- package/dist/views/page-builder/SEOPanel.js.map +0 -1
- package/dist/views/page-builder/SavedSections.d.ts +0 -6
- package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
- package/dist/views/page-builder/SavedSections.js +0 -141
- package/dist/views/page-builder/SavedSections.js.map +0 -1
- package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
- package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
- package/dist/views/page-builder/TemplatePicker.js +0 -68
- package/dist/views/page-builder/TemplatePicker.js.map +0 -1
- package/dist/views/page-builder/Widgets.d.ts +0 -13
- package/dist/views/page-builder/Widgets.d.ts.map +0 -1
- package/dist/views/page-builder/Widgets.js +0 -15
- package/dist/views/page-builder/Widgets.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/index.js +0 -25
- package/dist/views/page-builder/block-renderers/index.js.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
- package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/index.d.ts +0 -3
- package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/index.js +0 -2
- package/dist/views/page-builder/canvas/index.js.map +0 -1
- package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
- package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
- package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
- package/src/__tests__/views/page-builder-seo-panel.render.test.tsx +0 -77
- package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
- package/src/__tests__/views/page-templates.render.test.tsx +0 -182
- package/src/hooks/useBuilderState.ts +0 -361
- package/src/views/page-builder/AIBlockAssist.tsx +0 -64
- package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
- package/src/views/page-builder/BlockEditor.tsx +0 -523
- package/src/views/page-builder/BlockPicker.tsx +0 -324
- package/src/views/page-builder/BottomBar.tsx +0 -65
- package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
- package/src/views/page-builder/BuilderToolbar.tsx +0 -221
- package/src/views/page-builder/ContextPanel.tsx +0 -144
- package/src/views/page-builder/DesignScore.tsx +0 -251
- package/src/views/page-builder/NodeSettings.tsx +0 -500
- package/src/views/page-builder/PageBuilder.tsx +0 -405
- package/src/views/page-builder/PageSettings.tsx +0 -162
- package/src/views/page-builder/PageTemplates.tsx +0 -435
- package/src/views/page-builder/RelationshipField.tsx +0 -113
- package/src/views/page-builder/SEOPanel.tsx +0 -222
- package/src/views/page-builder/SavedSections.tsx +0 -480
- package/src/views/page-builder/TemplatePicker.tsx +0 -197
- package/src/views/page-builder/Widgets.tsx +0 -44
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
- package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
- package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
- package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
- package/src/views/page-builder/block-renderers/index.ts +0 -34
- package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
- package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
- package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
- package/src/views/page-builder/canvas/index.ts +0 -2
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ChevronLeft,
|
|
5
|
-
Undo2,
|
|
6
|
-
Redo2,
|
|
7
|
-
Monitor,
|
|
8
|
-
Tablet,
|
|
9
|
-
Smartphone,
|
|
10
|
-
Loader2,
|
|
11
|
-
Sparkles,
|
|
12
|
-
} from 'lucide-react'
|
|
13
|
-
import type { DeviceMode, PageSettings } from '../../hooks/useBuilderState.js'
|
|
14
|
-
|
|
15
|
-
export interface BuilderToolbarProps {
|
|
16
|
-
collectionSlug: string
|
|
17
|
-
pageSettings: PageSettings
|
|
18
|
-
status: 'DRAFT' | 'PUBLISHED' | 'SCHEDULED'
|
|
19
|
-
dirty: boolean
|
|
20
|
-
saving: boolean
|
|
21
|
-
canUndo: boolean
|
|
22
|
-
canRedo: boolean
|
|
23
|
-
deviceMode: DeviceMode
|
|
24
|
-
onNavigate: (path: string) => void
|
|
25
|
-
onTitleChange: (title: string) => void
|
|
26
|
-
onUndo: () => void
|
|
27
|
-
onRedo: () => void
|
|
28
|
-
onDeviceMode: (mode: DeviceMode) => void
|
|
29
|
-
onSave: () => void
|
|
30
|
-
onPublish: () => void
|
|
31
|
-
onOpenAI?: () => void
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const STATUS_STYLES: Record<string, string> = {
|
|
35
|
-
DRAFT: 'bg-muted text-muted-foreground',
|
|
36
|
-
PUBLISHED: 'bg-primary/10 text-primary',
|
|
37
|
-
SCHEDULED: 'bg-accent text-accent-foreground',
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function BuilderToolbar({
|
|
41
|
-
collectionSlug,
|
|
42
|
-
pageSettings,
|
|
43
|
-
status,
|
|
44
|
-
dirty,
|
|
45
|
-
saving,
|
|
46
|
-
canUndo,
|
|
47
|
-
canRedo,
|
|
48
|
-
deviceMode,
|
|
49
|
-
onNavigate,
|
|
50
|
-
onTitleChange,
|
|
51
|
-
onUndo,
|
|
52
|
-
onRedo,
|
|
53
|
-
onDeviceMode,
|
|
54
|
-
onSave,
|
|
55
|
-
onPublish,
|
|
56
|
-
onOpenAI,
|
|
57
|
-
}: BuilderToolbarProps) {
|
|
58
|
-
const collectionLabel = collectionSlug.charAt(0).toUpperCase() + collectionSlug.slice(1)
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div
|
|
62
|
-
className="bg-card border-border flex h-14 shrink-0 items-center gap-3 border-b px-4"
|
|
63
|
-
role="toolbar"
|
|
64
|
-
aria-label="Page builder toolbar"
|
|
65
|
-
>
|
|
66
|
-
{/* Back button */}
|
|
67
|
-
<button
|
|
68
|
-
onClick={() => onNavigate(`/collections/${collectionSlug}`)}
|
|
69
|
-
className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-sm transition-colors"
|
|
70
|
-
aria-label={`Back to ${collectionLabel}`}
|
|
71
|
-
>
|
|
72
|
-
<ChevronLeft size={16} />
|
|
73
|
-
<span className="hidden sm:inline">{collectionLabel}</span>
|
|
74
|
-
</button>
|
|
75
|
-
|
|
76
|
-
{/* Divider */}
|
|
77
|
-
<div className="bg-border h-6 w-px" />
|
|
78
|
-
|
|
79
|
-
{/* Page title */}
|
|
80
|
-
<input
|
|
81
|
-
type="text"
|
|
82
|
-
value={pageSettings.title}
|
|
83
|
-
onChange={(e) => onTitleChange(e.target.value)}
|
|
84
|
-
placeholder="Untitled Page"
|
|
85
|
-
className="text-foreground focus-visible:ring-ring placeholder:text-muted-foreground min-w-0 flex-1 rounded-sm border-none bg-transparent text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
86
|
-
aria-label="Page title"
|
|
87
|
-
/>
|
|
88
|
-
|
|
89
|
-
{/* Status badge */}
|
|
90
|
-
<span
|
|
91
|
-
className={`rounded-md px-2 py-0.5 text-xs font-medium whitespace-nowrap ${STATUS_STYLES[status] ?? STATUS_STYLES.DRAFT}`}
|
|
92
|
-
>
|
|
93
|
-
{status}
|
|
94
|
-
</span>
|
|
95
|
-
|
|
96
|
-
{/* Unsaved indicator */}
|
|
97
|
-
{dirty && !saving && (
|
|
98
|
-
<span
|
|
99
|
-
className="bg-destructive h-2 w-2 shrink-0 rounded-full"
|
|
100
|
-
title="Unsaved changes"
|
|
101
|
-
aria-label="Unsaved changes"
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{/* Divider */}
|
|
106
|
-
<div className="bg-border h-6 w-px" />
|
|
107
|
-
|
|
108
|
-
{/* Undo / Redo */}
|
|
109
|
-
<div className="flex items-center gap-1">
|
|
110
|
-
<button
|
|
111
|
-
onClick={onUndo}
|
|
112
|
-
disabled={!canUndo}
|
|
113
|
-
className="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
114
|
-
aria-label="Undo"
|
|
115
|
-
>
|
|
116
|
-
<Undo2 size={16} />
|
|
117
|
-
</button>
|
|
118
|
-
<button
|
|
119
|
-
onClick={onRedo}
|
|
120
|
-
disabled={!canRedo}
|
|
121
|
-
className="text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1.5 transition-colors disabled:pointer-events-none disabled:opacity-30"
|
|
122
|
-
aria-label="Redo"
|
|
123
|
-
>
|
|
124
|
-
<Redo2 size={16} />
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Divider */}
|
|
129
|
-
<div className="bg-border h-6 w-px" />
|
|
130
|
-
|
|
131
|
-
{/* Device toggle */}
|
|
132
|
-
<div className="bg-muted flex items-center gap-0.5 rounded-md p-0.5">
|
|
133
|
-
<DeviceButton
|
|
134
|
-
active={deviceMode === 'desktop'}
|
|
135
|
-
onClick={() => onDeviceMode('desktop')}
|
|
136
|
-
label="Desktop view"
|
|
137
|
-
>
|
|
138
|
-
<Monitor size={16} />
|
|
139
|
-
</DeviceButton>
|
|
140
|
-
<DeviceButton
|
|
141
|
-
active={deviceMode === 'tablet'}
|
|
142
|
-
onClick={() => onDeviceMode('tablet')}
|
|
143
|
-
label="Tablet view"
|
|
144
|
-
>
|
|
145
|
-
<Tablet size={16} />
|
|
146
|
-
</DeviceButton>
|
|
147
|
-
<DeviceButton
|
|
148
|
-
active={deviceMode === 'mobile'}
|
|
149
|
-
onClick={() => onDeviceMode('mobile')}
|
|
150
|
-
label="Mobile view"
|
|
151
|
-
>
|
|
152
|
-
<Smartphone size={16} />
|
|
153
|
-
</DeviceButton>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Divider */}
|
|
157
|
-
<div className="bg-border h-6 w-px" />
|
|
158
|
-
|
|
159
|
-
{/* AI Generate */}
|
|
160
|
-
{onOpenAI && (
|
|
161
|
-
<>
|
|
162
|
-
<button
|
|
163
|
-
onClick={onOpenAI}
|
|
164
|
-
className="text-primary bg-primary/10 hover:bg-primary/20 flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-sm font-medium transition-colors"
|
|
165
|
-
aria-label="Generate page with AI"
|
|
166
|
-
>
|
|
167
|
-
<Sparkles size={14} />
|
|
168
|
-
<span className="hidden sm:inline">AI</span>
|
|
169
|
-
</button>
|
|
170
|
-
<div className="bg-border h-6 w-px" />
|
|
171
|
-
</>
|
|
172
|
-
)}
|
|
173
|
-
|
|
174
|
-
{/* Save / Publish */}
|
|
175
|
-
<div className="flex items-center gap-2">
|
|
176
|
-
<button
|
|
177
|
-
onClick={onSave}
|
|
178
|
-
disabled={saving || !dirty}
|
|
179
|
-
className="bg-muted text-muted-foreground hover:bg-muted/80 flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50"
|
|
180
|
-
>
|
|
181
|
-
{saving && <Loader2 size={14} className="animate-spin" />}
|
|
182
|
-
{saving ? 'Saving...' : 'Save'}
|
|
183
|
-
</button>
|
|
184
|
-
<button
|
|
185
|
-
onClick={onPublish}
|
|
186
|
-
disabled={saving}
|
|
187
|
-
className="bg-primary text-primary-foreground hover:bg-primary/90 rounded-md px-3 py-1.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-50"
|
|
188
|
-
>
|
|
189
|
-
Publish
|
|
190
|
-
</button>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function DeviceButton({
|
|
197
|
-
active,
|
|
198
|
-
onClick,
|
|
199
|
-
label,
|
|
200
|
-
children,
|
|
201
|
-
}: {
|
|
202
|
-
active: boolean
|
|
203
|
-
onClick: () => void
|
|
204
|
-
label: string
|
|
205
|
-
children: React.ReactNode
|
|
206
|
-
}) {
|
|
207
|
-
return (
|
|
208
|
-
<button
|
|
209
|
-
onClick={onClick}
|
|
210
|
-
className={`rounded-md p-1.5 transition-colors ${
|
|
211
|
-
active
|
|
212
|
-
? 'bg-background text-foreground shadow-sm'
|
|
213
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
214
|
-
}`}
|
|
215
|
-
aria-label={label}
|
|
216
|
-
aria-pressed={active}
|
|
217
|
-
>
|
|
218
|
-
{children}
|
|
219
|
-
</button>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useMemo } from 'react'
|
|
4
|
-
import * as Tabs from '@radix-ui/react-tabs'
|
|
5
|
-
import { Blocks, Settings2, FileText, Search, Palette } from 'lucide-react'
|
|
6
|
-
import type { BuilderNode, PageNode } from '@prenta/core'
|
|
7
|
-
import { BlockEditor } from './BlockEditor.js'
|
|
8
|
-
import { NodeSettings } from './NodeSettings.js'
|
|
9
|
-
import { PageSettingsEditor } from './PageSettings.js'
|
|
10
|
-
import { BuilderSEOPanel } from './SEOPanel.js'
|
|
11
|
-
import { DesignScorePanel } from './DesignScore.js'
|
|
12
|
-
import type { PageSettings } from '../../hooks/useBuilderState.js'
|
|
13
|
-
|
|
14
|
-
export interface ContextPanelProps {
|
|
15
|
-
activeTab: 'block' | 'node' | 'page' | 'seo' | 'design'
|
|
16
|
-
onTabChange: (tab: 'block' | 'node' | 'page' | 'seo' | 'design') => void
|
|
17
|
-
selectedNode: BuilderNode | null
|
|
18
|
-
tree: PageNode
|
|
19
|
-
pageSettings: PageSettings
|
|
20
|
-
onUpdateSettings: (id: string, settings: Record<string, unknown>) => void
|
|
21
|
-
onUpdateBlock: (id: string, data: Record<string, unknown>) => void
|
|
22
|
-
onRemoveNode: (id: string) => void
|
|
23
|
-
onDuplicateNode: (id: string) => void
|
|
24
|
-
onMoveNodeUp: (id: string) => void
|
|
25
|
-
onMoveNodeDown: (id: string) => void
|
|
26
|
-
onPageSettingsChange: (settings: Partial<PageSettings>) => void
|
|
27
|
-
onAddRow: (sectionId: string) => void
|
|
28
|
-
config: any
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type TabDef = {
|
|
32
|
-
value: 'block' | 'node' | 'page' | 'seo' | 'design'
|
|
33
|
-
label: string
|
|
34
|
-
icon: typeof Blocks
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const TABS: TabDef[] = [
|
|
38
|
-
{ value: 'block', label: 'Block', icon: Blocks },
|
|
39
|
-
{ value: 'node', label: 'Node', icon: Settings2 },
|
|
40
|
-
{ value: 'page', label: 'Page', icon: FileText },
|
|
41
|
-
{ value: 'seo', label: 'SEO', icon: Search },
|
|
42
|
-
{ value: 'design', label: 'Design', icon: Palette },
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
export function ContextPanel({
|
|
46
|
-
activeTab,
|
|
47
|
-
onTabChange,
|
|
48
|
-
selectedNode,
|
|
49
|
-
tree,
|
|
50
|
-
pageSettings,
|
|
51
|
-
onUpdateSettings,
|
|
52
|
-
onUpdateBlock,
|
|
53
|
-
onRemoveNode,
|
|
54
|
-
onDuplicateNode,
|
|
55
|
-
onMoveNodeUp,
|
|
56
|
-
onMoveNodeDown,
|
|
57
|
-
onPageSettingsChange,
|
|
58
|
-
onAddRow,
|
|
59
|
-
config,
|
|
60
|
-
}: ContextPanelProps) {
|
|
61
|
-
const availableTabs = useMemo(() => {
|
|
62
|
-
if (!selectedNode) return TABS.filter((t) => t.value !== 'block' && t.value !== 'node')
|
|
63
|
-
if (selectedNode.type === 'block') return TABS.filter((t) => t.value !== 'node')
|
|
64
|
-
return TABS.filter((t) => t.value !== 'block')
|
|
65
|
-
}, [selectedNode])
|
|
66
|
-
|
|
67
|
-
const effectiveTab = useMemo(() => {
|
|
68
|
-
if (availableTabs.some((t) => t.value === activeTab)) return activeTab
|
|
69
|
-
if (selectedNode?.type === 'block') return 'block'
|
|
70
|
-
if (selectedNode) return 'node'
|
|
71
|
-
return 'page'
|
|
72
|
-
}, [activeTab, availableTabs, selectedNode])
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="bg-card h-full w-full overflow-y-auto">
|
|
76
|
-
<Tabs.Root
|
|
77
|
-
value={effectiveTab}
|
|
78
|
-
onValueChange={(value) => onTabChange(value as ContextPanelProps['activeTab'])}
|
|
79
|
-
>
|
|
80
|
-
<Tabs.List className="border-border bg-muted/30 flex border-b">
|
|
81
|
-
{availableTabs.map((tab) => {
|
|
82
|
-
const Icon = tab.icon
|
|
83
|
-
return (
|
|
84
|
-
<Tabs.Trigger
|
|
85
|
-
key={tab.value}
|
|
86
|
-
value={tab.value}
|
|
87
|
-
className="text-muted-foreground hover:text-foreground data-[state=active]:text-foreground data-[state=active]:border-primary data-[state=active]:bg-background flex flex-1 items-center justify-center gap-1.5 px-2 py-2.5 text-xs font-medium transition-colors data-[state=active]:border-b-2"
|
|
88
|
-
>
|
|
89
|
-
<Icon size={14} />
|
|
90
|
-
<span>{tab.label}</span>
|
|
91
|
-
</Tabs.Trigger>
|
|
92
|
-
)
|
|
93
|
-
})}
|
|
94
|
-
</Tabs.List>
|
|
95
|
-
|
|
96
|
-
<Tabs.Content value="block">
|
|
97
|
-
{selectedNode?.type === 'block' && (
|
|
98
|
-
<BlockEditor
|
|
99
|
-
node={selectedNode}
|
|
100
|
-
onUpdateBlock={onUpdateBlock}
|
|
101
|
-
onUpdateSettings={onUpdateSettings}
|
|
102
|
-
onRemoveNode={onRemoveNode}
|
|
103
|
-
onDuplicateNode={onDuplicateNode}
|
|
104
|
-
config={config}
|
|
105
|
-
/>
|
|
106
|
-
)}
|
|
107
|
-
</Tabs.Content>
|
|
108
|
-
|
|
109
|
-
<Tabs.Content value="node">
|
|
110
|
-
{selectedNode &&
|
|
111
|
-
selectedNode.type !== 'block' &&
|
|
112
|
-
selectedNode.type !== 'page' &&
|
|
113
|
-
selectedNode.type !== 'savedSectionRef' && (
|
|
114
|
-
<NodeSettings
|
|
115
|
-
node={selectedNode}
|
|
116
|
-
onUpdateSettings={onUpdateSettings}
|
|
117
|
-
onRemoveNode={onRemoveNode}
|
|
118
|
-
onDuplicateNode={onDuplicateNode}
|
|
119
|
-
onMoveNodeUp={onMoveNodeUp}
|
|
120
|
-
onMoveNodeDown={onMoveNodeDown}
|
|
121
|
-
onAddRow={selectedNode.type === 'section' ? onAddRow : undefined}
|
|
122
|
-
/>
|
|
123
|
-
)}
|
|
124
|
-
</Tabs.Content>
|
|
125
|
-
|
|
126
|
-
<Tabs.Content value="page">
|
|
127
|
-
<PageSettingsEditor settings={pageSettings} onChange={onPageSettingsChange} />
|
|
128
|
-
</Tabs.Content>
|
|
129
|
-
|
|
130
|
-
<Tabs.Content value="seo">
|
|
131
|
-
<BuilderSEOPanel
|
|
132
|
-
tree={tree}
|
|
133
|
-
pageSettings={pageSettings}
|
|
134
|
-
onPageSettingsChange={onPageSettingsChange}
|
|
135
|
-
/>
|
|
136
|
-
</Tabs.Content>
|
|
137
|
-
|
|
138
|
-
<Tabs.Content value="design">
|
|
139
|
-
<DesignScorePanel tree={tree} />
|
|
140
|
-
</Tabs.Content>
|
|
141
|
-
</Tabs.Root>
|
|
142
|
-
</div>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useState, useMemo } from 'react'
|
|
4
|
-
import { analyzeDesign } from '@prenta/core/page-builder'
|
|
5
|
-
import type {
|
|
6
|
-
PageNode,
|
|
7
|
-
DesignAnalysis,
|
|
8
|
-
DesignCategory,
|
|
9
|
-
BuilderDesignCheck,
|
|
10
|
-
DesignSuggestion,
|
|
11
|
-
} from '@prenta/core'
|
|
12
|
-
import {
|
|
13
|
-
CheckCircle2,
|
|
14
|
-
AlertCircle,
|
|
15
|
-
XCircle,
|
|
16
|
-
ChevronDown,
|
|
17
|
-
ChevronUp,
|
|
18
|
-
Lightbulb,
|
|
19
|
-
Palette,
|
|
20
|
-
} from 'lucide-react'
|
|
21
|
-
|
|
22
|
-
export interface DesignScorePanelProps {
|
|
23
|
-
tree: PageNode
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function getScoreColor(score: number, max: number): string {
|
|
27
|
-
const ratio = max > 0 ? score / max : 0
|
|
28
|
-
if (ratio >= 0.8) return 'text-success'
|
|
29
|
-
if (ratio >= 0.5) return 'text-warning'
|
|
30
|
-
return 'text-destructive'
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getBarColor(score: number, max: number): string {
|
|
34
|
-
const ratio = max > 0 ? score / max : 0
|
|
35
|
-
if (ratio >= 0.8) return 'bg-success'
|
|
36
|
-
if (ratio >= 0.5) return 'bg-warning'
|
|
37
|
-
return 'bg-destructive'
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getScoreLabel(score: number): string {
|
|
41
|
-
if (score >= 80) return 'Excellent'
|
|
42
|
-
if (score >= 65) return 'Good'
|
|
43
|
-
if (score >= 50) return 'Needs Work'
|
|
44
|
-
return 'Poor'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function getRingStrokeColor(score: number): string {
|
|
48
|
-
if (score >= 80) return 'rgb(34, 197, 94)'
|
|
49
|
-
if (score >= 50) return 'rgb(245, 158, 11)'
|
|
50
|
-
return 'rgb(239, 68, 68)'
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function StatusIcon({ status }: { status: BuilderDesignCheck['status'] }) {
|
|
54
|
-
switch (status) {
|
|
55
|
-
case 'good':
|
|
56
|
-
return <CheckCircle2 size={14} className="text-success shrink-0" />
|
|
57
|
-
case 'warning':
|
|
58
|
-
return <AlertCircle size={14} className="text-warning shrink-0" />
|
|
59
|
-
case 'error':
|
|
60
|
-
return <XCircle size={14} className="text-destructive shrink-0" />
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function SeverityBadge({ severity }: { severity: DesignSuggestion['severity'] }) {
|
|
65
|
-
const classes: Record<DesignSuggestion['severity'], string> = {
|
|
66
|
-
important: 'bg-destructive/10 text-destructive',
|
|
67
|
-
suggestion: 'bg-warning/10 text-warning',
|
|
68
|
-
info: 'bg-info/10 text-info',
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<span className={`inline-block rounded px-1.5 py-0.5 text-xs font-medium ${classes[severity]}`}>
|
|
73
|
-
{severity}
|
|
74
|
-
</span>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function ScoreRing({ score }: { score: number }) {
|
|
79
|
-
const radius = 32
|
|
80
|
-
const strokeWidth = 6
|
|
81
|
-
const circumference = 2 * Math.PI * radius
|
|
82
|
-
const progress = Math.min(score, 100) / 100
|
|
83
|
-
const dashOffset = circumference * (1 - progress)
|
|
84
|
-
const size = (radius + strokeWidth) * 2
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<div className="flex flex-col items-center gap-2">
|
|
88
|
-
<div className="relative">
|
|
89
|
-
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className="-rotate-90">
|
|
90
|
-
<circle
|
|
91
|
-
cx={size / 2}
|
|
92
|
-
cy={size / 2}
|
|
93
|
-
r={radius}
|
|
94
|
-
fill="none"
|
|
95
|
-
stroke="var(--border)"
|
|
96
|
-
strokeWidth={strokeWidth}
|
|
97
|
-
/>
|
|
98
|
-
<circle
|
|
99
|
-
cx={size / 2}
|
|
100
|
-
cy={size / 2}
|
|
101
|
-
r={radius}
|
|
102
|
-
fill="none"
|
|
103
|
-
stroke={getRingStrokeColor(score)}
|
|
104
|
-
strokeWidth={strokeWidth}
|
|
105
|
-
strokeDasharray={circumference}
|
|
106
|
-
strokeDashoffset={dashOffset}
|
|
107
|
-
strokeLinecap="round"
|
|
108
|
-
className="transition-all duration-700 ease-out"
|
|
109
|
-
/>
|
|
110
|
-
</svg>
|
|
111
|
-
<div className="absolute inset-0 flex items-center justify-center">
|
|
112
|
-
<span className={`text-lg font-medium ${getScoreColor(score, 100)}`}>{score}</span>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
<span className="text-muted-foreground text-xs">{getScoreLabel(score)}</span>
|
|
116
|
-
</div>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function CategoryRow({
|
|
121
|
-
category,
|
|
122
|
-
expanded,
|
|
123
|
-
onToggle,
|
|
124
|
-
}: {
|
|
125
|
-
category: DesignCategory
|
|
126
|
-
expanded: boolean
|
|
127
|
-
onToggle: () => void
|
|
128
|
-
}) {
|
|
129
|
-
const percentage = category.maxScore > 0 ? (category.score / category.maxScore) * 100 : 0
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<div className="border-border border-b last:border-b-0">
|
|
133
|
-
<button
|
|
134
|
-
type="button"
|
|
135
|
-
className="hover:bg-muted/50 flex w-full flex-col gap-2 px-4 py-3 text-left transition-colors"
|
|
136
|
-
onClick={onToggle}
|
|
137
|
-
aria-expanded={expanded}
|
|
138
|
-
>
|
|
139
|
-
<div className="flex items-center justify-between">
|
|
140
|
-
<span className="text-foreground text-xs font-medium">{category.label}</span>
|
|
141
|
-
<div className="flex items-center gap-2">
|
|
142
|
-
<span className="text-muted-foreground text-xs">
|
|
143
|
-
{category.score}/{category.maxScore}
|
|
144
|
-
</span>
|
|
145
|
-
{expanded ? (
|
|
146
|
-
<ChevronUp size={14} className="text-muted-foreground" />
|
|
147
|
-
) : (
|
|
148
|
-
<ChevronDown size={14} className="text-muted-foreground" />
|
|
149
|
-
)}
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
<div className="bg-muted h-1.5 w-full overflow-hidden rounded-full">
|
|
153
|
-
<div
|
|
154
|
-
className={`h-full rounded-full transition-all duration-500 ease-out ${getBarColor(category.score, category.maxScore)}`}
|
|
155
|
-
style={{ width: `${percentage}%` }}
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
|
-
</button>
|
|
159
|
-
|
|
160
|
-
{expanded && category.checks.length > 0 && (
|
|
161
|
-
<div className="flex flex-col gap-2 px-4 pb-3">
|
|
162
|
-
{category.checks.map((check) => (
|
|
163
|
-
<div key={check.id} className="flex items-start gap-2 pl-1">
|
|
164
|
-
<StatusIcon status={check.status} />
|
|
165
|
-
<div className="flex flex-col">
|
|
166
|
-
<span className="text-foreground text-xs font-medium">{check.label}</span>
|
|
167
|
-
<span className="text-muted-foreground text-xs">{check.detail}</span>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
))}
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
</div>
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function SuggestionsList({ suggestions }: { suggestions: DesignSuggestion[] }) {
|
|
178
|
-
const sorted = useMemo(() => {
|
|
179
|
-
const order: Record<DesignSuggestion['severity'], number> = {
|
|
180
|
-
important: 0,
|
|
181
|
-
suggestion: 1,
|
|
182
|
-
info: 2,
|
|
183
|
-
}
|
|
184
|
-
return [...suggestions].sort((a, b) => order[a.severity] - order[b.severity])
|
|
185
|
-
}, [suggestions])
|
|
186
|
-
|
|
187
|
-
if (sorted.length === 0) return null
|
|
188
|
-
|
|
189
|
-
return (
|
|
190
|
-
<div className="border-border border-t px-4 py-3">
|
|
191
|
-
<div className="mb-3 flex items-center gap-2">
|
|
192
|
-
<Lightbulb size={14} className="text-muted-foreground" />
|
|
193
|
-
<span className="text-foreground text-xs font-medium">Suggestions</span>
|
|
194
|
-
</div>
|
|
195
|
-
<div className="flex flex-col gap-2.5">
|
|
196
|
-
{sorted.map((suggestion) => (
|
|
197
|
-
<div key={suggestion.id} className="flex items-start gap-2">
|
|
198
|
-
<SeverityBadge severity={suggestion.severity} />
|
|
199
|
-
<span className="text-muted-foreground text-xs leading-relaxed">
|
|
200
|
-
{suggestion.message}
|
|
201
|
-
</span>
|
|
202
|
-
</div>
|
|
203
|
-
))}
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function DesignScorePanel({ tree }: DesignScorePanelProps) {
|
|
210
|
-
const analysis = useMemo(() => analyzeDesign(tree), [tree])
|
|
211
|
-
const [expandedCategories, setExpandedCategories] = useState<string[]>([])
|
|
212
|
-
|
|
213
|
-
const toggleCategory = (name: string) => {
|
|
214
|
-
setExpandedCategories((prev) =>
|
|
215
|
-
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name],
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (!tree.children || tree.children.length === 0) {
|
|
220
|
-
return (
|
|
221
|
-
<div className="flex min-h-[200px] flex-col items-center justify-center p-6 text-center">
|
|
222
|
-
<Palette size={32} className="text-muted-foreground mb-3" />
|
|
223
|
-
<p className="text-foreground mb-1 text-sm font-medium">Design Score</p>
|
|
224
|
-
<p className="text-muted-foreground text-xs">
|
|
225
|
-
Add sections to your page to see the design analysis
|
|
226
|
-
</p>
|
|
227
|
-
</div>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return (
|
|
232
|
-
<div className="flex flex-col">
|
|
233
|
-
<div className="border-border flex flex-col items-center border-b py-5">
|
|
234
|
-
<ScoreRing score={analysis.score} />
|
|
235
|
-
</div>
|
|
236
|
-
|
|
237
|
-
<div className="flex flex-col">
|
|
238
|
-
{analysis.categories.map((category) => (
|
|
239
|
-
<CategoryRow
|
|
240
|
-
key={category.name}
|
|
241
|
-
category={category}
|
|
242
|
-
expanded={expandedCategories.includes(category.name)}
|
|
243
|
-
onToggle={() => toggleCategory(category.name)}
|
|
244
|
-
/>
|
|
245
|
-
))}
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
|
-
<SuggestionsList suggestions={analysis.suggestions} />
|
|
249
|
-
</div>
|
|
250
|
-
)
|
|
251
|
-
}
|