@actuate-media/cms-admin 0.4.0 → 0.7.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 +35 -0
- package/dist/AdminRoot.js.map +1 -1
- package/dist/actuate-admin.css +1 -1
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.js +1 -0
- package/dist/components/Breadcrumbs.js.map +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorBoundary.js.map +1 -1
- package/dist/hooks/useBuilderState.d.ts +49 -0
- package/dist/hooks/useBuilderState.d.ts.map +1 -0
- package/dist/hooks/useBuilderState.js +238 -0
- package/dist/hooks/useBuilderState.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +2 -2
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/views/ForgotPassword.d.ts +5 -0
- package/dist/views/ForgotPassword.d.ts.map +1 -0
- package/dist/views/ForgotPassword.js +41 -0
- package/dist/views/ForgotPassword.js.map +1 -0
- package/dist/views/ResetPassword.d.ts +6 -0
- package/dist/views/ResetPassword.d.ts.map +1 -0
- package/dist/views/ResetPassword.js +46 -0
- package/dist/views/ResetPassword.js.map +1 -0
- package/dist/views/ScriptTagEditor.d.ts +6 -0
- package/dist/views/ScriptTagEditor.d.ts.map +1 -0
- package/dist/views/ScriptTagEditor.js +109 -0
- package/dist/views/ScriptTagEditor.js.map +1 -0
- package/dist/views/ScriptTags.d.ts +5 -0
- package/dist/views/ScriptTags.d.ts.map +1 -0
- package/dist/views/ScriptTags.js +54 -0
- package/dist/views/ScriptTags.js.map +1 -0
- package/dist/views/page-builder/AIBlockAssist.d.ts +9 -0
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +1 -0
- package/dist/views/page-builder/AIBlockAssist.js +40 -0
- package/dist/views/page-builder/AIBlockAssist.js.map +1 -0
- package/dist/views/page-builder/AIGenerateDialog.d.ts +8 -0
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -0
- package/dist/views/page-builder/AIGenerateDialog.js +170 -0
- package/dist/views/page-builder/AIGenerateDialog.js.map +1 -0
- package/dist/views/page-builder/BlockEditor.d.ts +11 -0
- package/dist/views/page-builder/BlockEditor.d.ts.map +1 -0
- package/dist/views/page-builder/BlockEditor.js +67 -0
- package/dist/views/page-builder/BlockEditor.js.map +1 -0
- package/dist/views/page-builder/BlockPicker.d.ts +7 -0
- package/dist/views/page-builder/BlockPicker.d.ts.map +1 -0
- package/dist/views/page-builder/BlockPicker.js +102 -0
- package/dist/views/page-builder/BlockPicker.js.map +1 -0
- package/dist/views/page-builder/BottomBar.d.ts +9 -0
- package/dist/views/page-builder/BottomBar.d.ts.map +1 -0
- package/dist/views/page-builder/BottomBar.js +13 -0
- package/dist/views/page-builder/BottomBar.js.map +1 -0
- package/dist/views/page-builder/BuilderToolbar.d.ts +21 -0
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +1 -0
- package/dist/views/page-builder/BuilderToolbar.js +18 -0
- package/dist/views/page-builder/BuilderToolbar.js.map +1 -0
- package/dist/views/page-builder/ContextPanel.d.ts +20 -0
- package/dist/views/page-builder/ContextPanel.d.ts.map +1 -0
- package/dist/views/page-builder/ContextPanel.js +40 -0
- package/dist/views/page-builder/ContextPanel.js.map +1 -0
- package/dist/views/page-builder/DesignScore.d.ts +6 -0
- package/dist/views/page-builder/DesignScore.d.ts.map +1 -0
- package/dist/views/page-builder/DesignScore.js +93 -0
- package/dist/views/page-builder/DesignScore.js.map +1 -0
- package/dist/views/page-builder/NodeSettings.d.ts +12 -0
- package/dist/views/page-builder/NodeSettings.d.ts.map +1 -0
- package/dist/views/page-builder/NodeSettings.js +80 -0
- package/dist/views/page-builder/NodeSettings.js.map +1 -0
- package/dist/views/page-builder/PageBuilder.d.ts +8 -0
- package/dist/views/page-builder/PageBuilder.d.ts.map +1 -0
- package/dist/views/page-builder/PageBuilder.js +126 -0
- package/dist/views/page-builder/PageBuilder.js.map +1 -0
- package/dist/views/page-builder/PageSettings.d.ts +7 -0
- package/dist/views/page-builder/PageSettings.d.ts.map +1 -0
- package/dist/views/page-builder/PageSettings.js +27 -0
- package/dist/views/page-builder/PageSettings.js.map +1 -0
- package/dist/views/page-builder/SEOPanel.d.ts +10 -0
- package/dist/views/page-builder/SEOPanel.d.ts.map +1 -0
- package/dist/views/page-builder/SEOPanel.js +105 -0
- package/dist/views/page-builder/SEOPanel.js.map +1 -0
- package/dist/views/page-builder/SavedSections.d.ts +6 -0
- package/dist/views/page-builder/SavedSections.d.ts.map +1 -0
- package/dist/views/page-builder/SavedSections.js +145 -0
- package/dist/views/page-builder/SavedSections.js.map +1 -0
- package/dist/views/page-builder/TemplatePicker.d.ts +7 -0
- package/dist/views/page-builder/TemplatePicker.d.ts.map +1 -0
- package/dist/views/page-builder/TemplatePicker.js +68 -0
- package/dist/views/page-builder/TemplatePicker.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.js +19 -0
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.js +22 -0
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/CodePreview.js +16 -0
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.js +24 -0
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +6 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +7 -0
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/FormPreview.js +14 -0
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +21 -0
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.js +19 -0
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.js +17 -0
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/TextPreview.js +26 -0
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +3 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.js +21 -0
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +1 -0
- package/dist/views/page-builder/block-renderers/index.d.ts +9 -0
- package/dist/views/page-builder/block-renderers/index.d.ts.map +1 -0
- package/dist/views/page-builder/block-renderers/index.js +25 -0
- package/dist/views/page-builder/block-renderers/index.js.map +1 -0
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/BlockRenderer.js +30 -0
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +10 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.js +26 -0
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +1 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.js +36 -0
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.js +33 -0
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/RowRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/RowRenderer.js +32 -0
- package/dist/views/page-builder/canvas/RowRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts +8 -0
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/SectionRenderer.js +54 -0
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +1 -0
- package/dist/views/page-builder/canvas/index.d.ts +3 -0
- package/dist/views/page-builder/canvas/index.d.ts.map +1 -0
- package/dist/views/page-builder/canvas/index.js +2 -0
- package/dist/views/page-builder/canvas/index.js.map +1 -0
- package/package.json +7 -4
- package/src/AdminRoot.tsx +41 -0
- package/src/components/Breadcrumbs.tsx +1 -0
- package/src/components/ErrorBoundary.tsx +3 -3
- package/src/hooks/useBuilderState.ts +328 -0
- package/src/index.ts +8 -0
- package/src/layout/Sidebar.tsx +7 -0
- package/src/views/ForgotPassword.tsx +136 -0
- package/src/views/ResetPassword.tsx +192 -0
- package/src/views/ScriptTagEditor.tsx +361 -0
- package/src/views/ScriptTags.tsx +174 -0
- package/src/views/page-builder/AIBlockAssist.tsx +68 -0
- package/src/views/page-builder/AIGenerateDialog.tsx +574 -0
- package/src/views/page-builder/BlockEditor.tsx +352 -0
- package/src/views/page-builder/BlockPicker.tsx +338 -0
- package/src/views/page-builder/BottomBar.tsx +64 -0
- package/src/views/page-builder/BuilderToolbar.tsx +218 -0
- package/src/views/page-builder/ContextPanel.tsx +145 -0
- package/src/views/page-builder/DesignScore.tsx +258 -0
- package/src/views/page-builder/NodeSettings.tsx +515 -0
- package/src/views/page-builder/PageBuilder.tsx +288 -0
- package/src/views/page-builder/PageSettings.tsx +161 -0
- package/src/views/page-builder/SEOPanel.tsx +485 -0
- package/src/views/page-builder/SavedSections.tsx +486 -0
- package/src/views/page-builder/TemplatePicker.tsx +201 -0
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +81 -0
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +71 -0
- package/src/views/page-builder/block-renderers/CodePreview.tsx +46 -0
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +90 -0
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +18 -0
- package/src/views/page-builder/block-renderers/FormPreview.tsx +69 -0
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +93 -0
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +103 -0
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +54 -0
- package/src/views/page-builder/block-renderers/TextPreview.tsx +81 -0
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +78 -0
- package/src/views/page-builder/block-renderers/index.ts +34 -0
- package/src/views/page-builder/canvas/BlockRenderer.tsx +62 -0
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +90 -0
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +86 -0
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +71 -0
- package/src/views/page-builder/canvas/RowRenderer.tsx +72 -0
- package/src/views/page-builder/canvas/SectionRenderer.tsx +97 -0
- package/src/views/page-builder/canvas/index.ts +2 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
ArrowUp,
|
|
6
|
+
ArrowDown,
|
|
7
|
+
Copy,
|
|
8
|
+
Trash2,
|
|
9
|
+
Plus,
|
|
10
|
+
AlignLeft,
|
|
11
|
+
AlignCenter,
|
|
12
|
+
AlignRight,
|
|
13
|
+
ArrowUpFromLine,
|
|
14
|
+
ArrowDownToLine,
|
|
15
|
+
Minus,
|
|
16
|
+
} from 'lucide-react';
|
|
17
|
+
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
|
18
|
+
import type {
|
|
19
|
+
BuilderNode,
|
|
20
|
+
SectionNode,
|
|
21
|
+
ContainerNode,
|
|
22
|
+
RowNode,
|
|
23
|
+
ColumnNode,
|
|
24
|
+
} from '@actuate-media/cms-core';
|
|
25
|
+
|
|
26
|
+
export interface NodeSettingsProps {
|
|
27
|
+
node: SectionNode | ContainerNode | RowNode | ColumnNode;
|
|
28
|
+
onUpdateSettings: (id: string, settings: Record<string, unknown>) => void;
|
|
29
|
+
onRemoveNode: (id: string) => void;
|
|
30
|
+
onDuplicateNode: (id: string) => void;
|
|
31
|
+
onMoveNodeUp: (id: string) => void;
|
|
32
|
+
onMoveNodeDown: (id: string) => void;
|
|
33
|
+
onAddRow?: (sectionId: string) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const INPUT_CLASS =
|
|
37
|
+
'w-full px-3 py-2 text-sm bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring';
|
|
38
|
+
const LABEL_CLASS = 'text-sm font-medium text-foreground mb-1 block';
|
|
39
|
+
const SECTION_HEADING_CLASS = 'text-xs font-medium uppercase tracking-wider text-muted-foreground mb-2';
|
|
40
|
+
|
|
41
|
+
const COLUMN_PRESETS: { label: string; widths: number[] }[] = [
|
|
42
|
+
{ label: '12', widths: [12] },
|
|
43
|
+
{ label: '6 | 6', widths: [6, 6] },
|
|
44
|
+
{ label: '4 | 4 | 4', widths: [4, 4, 4] },
|
|
45
|
+
{ label: '3 | 3 | 3 | 3', widths: [3, 3, 3, 3] },
|
|
46
|
+
{ label: '8 | 4', widths: [8, 4] },
|
|
47
|
+
{ label: '4 | 8', widths: [4, 8] },
|
|
48
|
+
{ label: '3 | 6 | 3', widths: [3, 6, 3] },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export function NodeSettings({
|
|
52
|
+
node,
|
|
53
|
+
onUpdateSettings,
|
|
54
|
+
onRemoveNode,
|
|
55
|
+
onDuplicateNode,
|
|
56
|
+
onMoveNodeUp,
|
|
57
|
+
onMoveNodeDown,
|
|
58
|
+
onAddRow,
|
|
59
|
+
}: NodeSettingsProps) {
|
|
60
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
61
|
+
|
|
62
|
+
const updateSetting = useCallback(
|
|
63
|
+
(key: string, value: unknown) => {
|
|
64
|
+
onUpdateSettings(node.id, { [key]: value });
|
|
65
|
+
},
|
|
66
|
+
[node.id, onUpdateSettings]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const handleDelete = useCallback(() => {
|
|
70
|
+
if (confirmDelete) {
|
|
71
|
+
onRemoveNode(node.id);
|
|
72
|
+
setConfirmDelete(false);
|
|
73
|
+
} else {
|
|
74
|
+
setConfirmDelete(true);
|
|
75
|
+
}
|
|
76
|
+
}, [confirmDelete, node.id, onRemoveNode]);
|
|
77
|
+
|
|
78
|
+
const nodeLabel =
|
|
79
|
+
node.type === 'section'
|
|
80
|
+
? 'Section'
|
|
81
|
+
: node.type === 'container'
|
|
82
|
+
? 'Container'
|
|
83
|
+
: node.type === 'row'
|
|
84
|
+
? 'Row'
|
|
85
|
+
: 'Column';
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex flex-col h-full">
|
|
89
|
+
<div className="p-4 border-b border-border">
|
|
90
|
+
<p className="text-sm font-medium text-foreground">{nodeLabel} Settings</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="flex-1 overflow-y-auto">
|
|
94
|
+
<div className="space-y-4 p-4">
|
|
95
|
+
{node.type === 'section' && <SectionFields node={node} updateSetting={updateSetting} onAddRow={onAddRow} />}
|
|
96
|
+
{node.type === 'container' && <ContainerFields node={node} updateSetting={updateSetting} />}
|
|
97
|
+
{node.type === 'row' && <RowFields node={node} updateSetting={updateSetting} />}
|
|
98
|
+
{node.type === 'column' && <ColumnFields node={node} updateSetting={updateSetting} />}
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="p-4 border-t border-border space-y-2">
|
|
103
|
+
<p className={SECTION_HEADING_CLASS}>Actions</p>
|
|
104
|
+
<div className="flex gap-2">
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
onClick={() => onMoveNodeUp(node.id)}
|
|
108
|
+
aria-label="Move up"
|
|
109
|
+
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm bg-background border border-input rounded-md hover:bg-accent transition-colors"
|
|
110
|
+
>
|
|
111
|
+
<ArrowUp size={14} />
|
|
112
|
+
Up
|
|
113
|
+
</button>
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => onMoveNodeDown(node.id)}
|
|
117
|
+
aria-label="Move down"
|
|
118
|
+
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm bg-background border border-input rounded-md hover:bg-accent transition-colors"
|
|
119
|
+
>
|
|
120
|
+
<ArrowDown size={14} />
|
|
121
|
+
Down
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={() => onDuplicateNode(node.id)}
|
|
127
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium bg-background border border-input rounded-md hover:bg-accent transition-colors"
|
|
128
|
+
>
|
|
129
|
+
<Copy size={14} />
|
|
130
|
+
Duplicate
|
|
131
|
+
</button>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={handleDelete}
|
|
135
|
+
onBlur={() => setConfirmDelete(false)}
|
|
136
|
+
className={`w-full flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium rounded-md transition-colors ${
|
|
137
|
+
confirmDelete
|
|
138
|
+
? 'bg-destructive text-destructive-foreground'
|
|
139
|
+
: 'bg-background border border-destructive text-destructive hover:bg-destructive/10'
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
<Trash2 size={14} />
|
|
143
|
+
{confirmDelete ? 'Click again to confirm' : 'Delete'}
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function SectionFields({
|
|
151
|
+
node,
|
|
152
|
+
updateSetting,
|
|
153
|
+
onAddRow,
|
|
154
|
+
}: {
|
|
155
|
+
node: SectionNode;
|
|
156
|
+
updateSetting: (key: string, value: unknown) => void;
|
|
157
|
+
onAddRow?: (sectionId: string) => void;
|
|
158
|
+
}) {
|
|
159
|
+
const s = node.settings;
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<p className={SECTION_HEADING_CLASS}>Background</p>
|
|
163
|
+
<div>
|
|
164
|
+
<label className={LABEL_CLASS}>Background Color</label>
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
<input
|
|
167
|
+
type="color"
|
|
168
|
+
value={s.background ?? '#ffffff'}
|
|
169
|
+
onChange={(e) => updateSetting('background', e.target.value)}
|
|
170
|
+
className="h-9 w-9 rounded-md border border-input cursor-pointer"
|
|
171
|
+
/>
|
|
172
|
+
<input
|
|
173
|
+
type="text"
|
|
174
|
+
value={s.background ?? ''}
|
|
175
|
+
onChange={(e) => updateSetting('background', e.target.value)}
|
|
176
|
+
placeholder="transparent"
|
|
177
|
+
className={`${INPUT_CLASS} flex-1`}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<p className={SECTION_HEADING_CLASS}>Spacing</p>
|
|
183
|
+
<div className="grid grid-cols-2 gap-3">
|
|
184
|
+
<div>
|
|
185
|
+
<label className={LABEL_CLASS}>Padding Top</label>
|
|
186
|
+
<input
|
|
187
|
+
type="text"
|
|
188
|
+
value={s.paddingTop ?? ''}
|
|
189
|
+
onChange={(e) => updateSetting('paddingTop', e.target.value)}
|
|
190
|
+
placeholder="0px"
|
|
191
|
+
className={INPUT_CLASS}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
<div>
|
|
195
|
+
<label className={LABEL_CLASS}>Padding Bottom</label>
|
|
196
|
+
<input
|
|
197
|
+
type="text"
|
|
198
|
+
value={s.paddingBottom ?? ''}
|
|
199
|
+
onChange={(e) => updateSetting('paddingBottom', e.target.value)}
|
|
200
|
+
placeholder="0px"
|
|
201
|
+
className={INPUT_CLASS}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
<div>
|
|
205
|
+
<label className={LABEL_CLASS}>Margin Top</label>
|
|
206
|
+
<input
|
|
207
|
+
type="text"
|
|
208
|
+
value={s.marginTop ?? ''}
|
|
209
|
+
onChange={(e) => updateSetting('marginTop', e.target.value)}
|
|
210
|
+
placeholder="0px"
|
|
211
|
+
className={INPUT_CLASS}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<div>
|
|
215
|
+
<label className={LABEL_CLASS}>Margin Bottom</label>
|
|
216
|
+
<input
|
|
217
|
+
type="text"
|
|
218
|
+
value={s.marginBottom ?? ''}
|
|
219
|
+
onChange={(e) => updateSetting('marginBottom', e.target.value)}
|
|
220
|
+
placeholder="0px"
|
|
221
|
+
className={INPUT_CLASS}
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<p className={SECTION_HEADING_CLASS}>Attributes</p>
|
|
227
|
+
<div className="flex items-center justify-between">
|
|
228
|
+
<label className="text-sm font-medium text-foreground">Visibility</label>
|
|
229
|
+
<SwitchPrimitive.Root
|
|
230
|
+
checked={s.visibility !== 'hidden'}
|
|
231
|
+
onCheckedChange={(checked) =>
|
|
232
|
+
updateSetting('visibility', checked ? 'visible' : 'hidden')
|
|
233
|
+
}
|
|
234
|
+
className="w-9 h-5 bg-input rounded-full relative data-[state=checked]:bg-primary transition-colors"
|
|
235
|
+
aria-label="Section visibility"
|
|
236
|
+
>
|
|
237
|
+
<SwitchPrimitive.Thumb className="block h-3.5 w-3.5 rounded-full bg-background shadow-sm transition-transform translate-x-0.5 data-[state=checked]:translate-x-[18px]" />
|
|
238
|
+
</SwitchPrimitive.Root>
|
|
239
|
+
</div>
|
|
240
|
+
<div>
|
|
241
|
+
<label className={LABEL_CLASS}>HTML ID</label>
|
|
242
|
+
<input
|
|
243
|
+
type="text"
|
|
244
|
+
value={s.htmlId ?? ''}
|
|
245
|
+
onChange={(e) => updateSetting('htmlId', e.target.value)}
|
|
246
|
+
placeholder="section-id"
|
|
247
|
+
className={INPUT_CLASS}
|
|
248
|
+
/>
|
|
249
|
+
</div>
|
|
250
|
+
<div>
|
|
251
|
+
<label className={LABEL_CLASS}>HTML Class</label>
|
|
252
|
+
<input
|
|
253
|
+
type="text"
|
|
254
|
+
value={s.htmlClass ?? ''}
|
|
255
|
+
onChange={(e) => updateSetting('htmlClass', e.target.value)}
|
|
256
|
+
placeholder="my-class"
|
|
257
|
+
className={INPUT_CLASS}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{onAddRow && (
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
onClick={() => onAddRow(node.id)}
|
|
265
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
|
266
|
+
>
|
|
267
|
+
<Plus size={14} />
|
|
268
|
+
Add Row
|
|
269
|
+
</button>
|
|
270
|
+
)}
|
|
271
|
+
</>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function ContainerFields({
|
|
276
|
+
node,
|
|
277
|
+
updateSetting,
|
|
278
|
+
}: {
|
|
279
|
+
node: ContainerNode;
|
|
280
|
+
updateSetting: (key: string, value: unknown) => void;
|
|
281
|
+
}) {
|
|
282
|
+
const s = node.settings;
|
|
283
|
+
return (
|
|
284
|
+
<>
|
|
285
|
+
<p className={SECTION_HEADING_CLASS}>Layout</p>
|
|
286
|
+
<div>
|
|
287
|
+
<label className={LABEL_CLASS}>Max Width</label>
|
|
288
|
+
<input
|
|
289
|
+
type="text"
|
|
290
|
+
value={s.maxWidth ?? ''}
|
|
291
|
+
onChange={(e) => updateSetting('maxWidth', e.target.value)}
|
|
292
|
+
placeholder="1200px"
|
|
293
|
+
className={INPUT_CLASS}
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
<div>
|
|
297
|
+
<label className={LABEL_CLASS}>Alignment</label>
|
|
298
|
+
<div className="flex gap-1">
|
|
299
|
+
{(['left', 'center', 'right'] as const).map((align) => {
|
|
300
|
+
const Icon = align === 'left' ? AlignLeft : align === 'center' ? AlignCenter : AlignRight;
|
|
301
|
+
return (
|
|
302
|
+
<button
|
|
303
|
+
key={align}
|
|
304
|
+
type="button"
|
|
305
|
+
onClick={() => updateSetting('alignment', align)}
|
|
306
|
+
aria-label={`Align ${align}`}
|
|
307
|
+
className={`flex-1 flex items-center justify-center py-2 rounded-md border transition-colors ${
|
|
308
|
+
s.alignment === align
|
|
309
|
+
? 'bg-primary text-primary-foreground border-primary'
|
|
310
|
+
: 'bg-background border-input hover:bg-accent'
|
|
311
|
+
}`}
|
|
312
|
+
>
|
|
313
|
+
<Icon size={14} />
|
|
314
|
+
</button>
|
|
315
|
+
);
|
|
316
|
+
})}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
<div>
|
|
320
|
+
<label className={LABEL_CLASS}>Padding</label>
|
|
321
|
+
<input
|
|
322
|
+
type="text"
|
|
323
|
+
value={s.padding ?? ''}
|
|
324
|
+
onChange={(e) => updateSetting('padding', e.target.value)}
|
|
325
|
+
placeholder="0px"
|
|
326
|
+
className={INPUT_CLASS}
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
</>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function RowFields({
|
|
334
|
+
node,
|
|
335
|
+
updateSetting,
|
|
336
|
+
}: {
|
|
337
|
+
node: RowNode;
|
|
338
|
+
updateSetting: (key: string, value: unknown) => void;
|
|
339
|
+
}) {
|
|
340
|
+
const s = node.settings;
|
|
341
|
+
return (
|
|
342
|
+
<>
|
|
343
|
+
<p className={SECTION_HEADING_CLASS}>Layout</p>
|
|
344
|
+
<div>
|
|
345
|
+
<label className={LABEL_CLASS}>Gap</label>
|
|
346
|
+
<input
|
|
347
|
+
type="text"
|
|
348
|
+
value={s.gap ?? ''}
|
|
349
|
+
onChange={(e) => updateSetting('gap', e.target.value)}
|
|
350
|
+
placeholder="16px"
|
|
351
|
+
className={INPUT_CLASS}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
<div>
|
|
355
|
+
<label className={LABEL_CLASS}>Vertical Align</label>
|
|
356
|
+
<div className="flex gap-1">
|
|
357
|
+
{(['top', 'center', 'bottom', 'stretch'] as const).map((align) => {
|
|
358
|
+
const Icon =
|
|
359
|
+
align === 'top'
|
|
360
|
+
? ArrowUpFromLine
|
|
361
|
+
: align === 'bottom'
|
|
362
|
+
? ArrowDownToLine
|
|
363
|
+
: align === 'center'
|
|
364
|
+
? Minus
|
|
365
|
+
: AlignCenter;
|
|
366
|
+
return (
|
|
367
|
+
<button
|
|
368
|
+
key={align}
|
|
369
|
+
type="button"
|
|
370
|
+
onClick={() => updateSetting('verticalAlign', align)}
|
|
371
|
+
aria-label={`Align ${align}`}
|
|
372
|
+
className={`flex-1 flex items-center justify-center py-2 rounded-md border text-xs transition-colors ${
|
|
373
|
+
s.verticalAlign === align
|
|
374
|
+
? 'bg-primary text-primary-foreground border-primary'
|
|
375
|
+
: 'bg-background border-input hover:bg-accent'
|
|
376
|
+
}`}
|
|
377
|
+
>
|
|
378
|
+
<Icon size={14} />
|
|
379
|
+
</button>
|
|
380
|
+
);
|
|
381
|
+
})}
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<p className={SECTION_HEADING_CLASS}>Mobile</p>
|
|
386
|
+
<div className="flex items-center justify-between">
|
|
387
|
+
<label className="text-sm font-medium text-foreground">Reverse on Mobile</label>
|
|
388
|
+
<SwitchPrimitive.Root
|
|
389
|
+
checked={!!s.reverseOnMobile}
|
|
390
|
+
onCheckedChange={(checked) => updateSetting('reverseOnMobile', checked)}
|
|
391
|
+
className="w-9 h-5 bg-input rounded-full relative data-[state=checked]:bg-primary transition-colors"
|
|
392
|
+
aria-label="Reverse column order on mobile"
|
|
393
|
+
>
|
|
394
|
+
<SwitchPrimitive.Thumb className="block h-3.5 w-3.5 rounded-full bg-background shadow-sm transition-transform translate-x-0.5 data-[state=checked]:translate-x-[18px]" />
|
|
395
|
+
</SwitchPrimitive.Root>
|
|
396
|
+
</div>
|
|
397
|
+
<div className="flex items-center justify-between">
|
|
398
|
+
<label className="text-sm font-medium text-foreground">Wrap on Mobile</label>
|
|
399
|
+
<SwitchPrimitive.Root
|
|
400
|
+
checked={!!s.wrapOnMobile}
|
|
401
|
+
onCheckedChange={(checked) => updateSetting('wrapOnMobile', checked)}
|
|
402
|
+
className="w-9 h-5 bg-input rounded-full relative data-[state=checked]:bg-primary transition-colors"
|
|
403
|
+
aria-label="Wrap columns on mobile"
|
|
404
|
+
>
|
|
405
|
+
<SwitchPrimitive.Thumb className="block h-3.5 w-3.5 rounded-full bg-background shadow-sm transition-transform translate-x-0.5 data-[state=checked]:translate-x-[18px]" />
|
|
406
|
+
</SwitchPrimitive.Root>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<p className={SECTION_HEADING_CLASS}>Column Presets</p>
|
|
410
|
+
<div className="grid grid-cols-2 gap-1.5">
|
|
411
|
+
{COLUMN_PRESETS.map((preset) => (
|
|
412
|
+
<button
|
|
413
|
+
key={preset.label}
|
|
414
|
+
type="button"
|
|
415
|
+
onClick={() => updateSetting('__columnPreset', preset.widths)}
|
|
416
|
+
className="px-2 py-2 rounded-md border border-input bg-background hover:bg-accent transition-colors"
|
|
417
|
+
>
|
|
418
|
+
<div className="flex gap-0.5 h-4">
|
|
419
|
+
{preset.widths.map((w, i) => (
|
|
420
|
+
<div
|
|
421
|
+
key={i}
|
|
422
|
+
className="bg-muted-foreground/30 rounded-sm"
|
|
423
|
+
style={{ flex: w }}
|
|
424
|
+
/>
|
|
425
|
+
))}
|
|
426
|
+
</div>
|
|
427
|
+
<p className="text-xs text-muted-foreground mt-1 text-center">
|
|
428
|
+
{preset.widths.join(' | ')}
|
|
429
|
+
</p>
|
|
430
|
+
</button>
|
|
431
|
+
))}
|
|
432
|
+
</div>
|
|
433
|
+
</>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function ColumnFields({
|
|
438
|
+
node,
|
|
439
|
+
updateSetting,
|
|
440
|
+
}: {
|
|
441
|
+
node: ColumnNode;
|
|
442
|
+
updateSetting: (key: string, value: unknown) => void;
|
|
443
|
+
}) {
|
|
444
|
+
const s = node.settings;
|
|
445
|
+
return (
|
|
446
|
+
<>
|
|
447
|
+
<p className={SECTION_HEADING_CLASS}>Size</p>
|
|
448
|
+
<div>
|
|
449
|
+
<label className={LABEL_CLASS}>Width (1-12)</label>
|
|
450
|
+
<input
|
|
451
|
+
type="number"
|
|
452
|
+
min={1}
|
|
453
|
+
max={12}
|
|
454
|
+
value={s.width}
|
|
455
|
+
onChange={(e) => updateSetting('width', Number(e.target.value))}
|
|
456
|
+
className={INPUT_CLASS}
|
|
457
|
+
/>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<p className={SECTION_HEADING_CLASS}>Layout</p>
|
|
461
|
+
<div>
|
|
462
|
+
<label className={LABEL_CLASS}>Vertical Align</label>
|
|
463
|
+
<div className="flex gap-1">
|
|
464
|
+
{(['top', 'center', 'bottom'] as const).map((align) => {
|
|
465
|
+
const Icon =
|
|
466
|
+
align === 'top' ? ArrowUpFromLine : align === 'bottom' ? ArrowDownToLine : Minus;
|
|
467
|
+
return (
|
|
468
|
+
<button
|
|
469
|
+
key={align}
|
|
470
|
+
type="button"
|
|
471
|
+
onClick={() => updateSetting('verticalAlign', align)}
|
|
472
|
+
aria-label={`Align ${align}`}
|
|
473
|
+
className={`flex-1 flex items-center justify-center py-2 rounded-md border transition-colors ${
|
|
474
|
+
s.verticalAlign === align
|
|
475
|
+
? 'bg-primary text-primary-foreground border-primary'
|
|
476
|
+
: 'bg-background border-input hover:bg-accent'
|
|
477
|
+
}`}
|
|
478
|
+
>
|
|
479
|
+
<Icon size={14} />
|
|
480
|
+
</button>
|
|
481
|
+
);
|
|
482
|
+
})}
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
<div>
|
|
486
|
+
<label className={LABEL_CLASS}>Padding</label>
|
|
487
|
+
<input
|
|
488
|
+
type="text"
|
|
489
|
+
value={s.padding ?? ''}
|
|
490
|
+
onChange={(e) => updateSetting('padding', e.target.value)}
|
|
491
|
+
placeholder="0px"
|
|
492
|
+
className={INPUT_CLASS}
|
|
493
|
+
/>
|
|
494
|
+
</div>
|
|
495
|
+
<div>
|
|
496
|
+
<label className={LABEL_CLASS}>Background Color</label>
|
|
497
|
+
<div className="flex items-center gap-2">
|
|
498
|
+
<input
|
|
499
|
+
type="color"
|
|
500
|
+
value={s.background ?? '#ffffff'}
|
|
501
|
+
onChange={(e) => updateSetting('background', e.target.value)}
|
|
502
|
+
className="h-9 w-9 rounded-md border border-input cursor-pointer"
|
|
503
|
+
/>
|
|
504
|
+
<input
|
|
505
|
+
type="text"
|
|
506
|
+
value={s.background ?? ''}
|
|
507
|
+
onChange={(e) => updateSetting('background', e.target.value)}
|
|
508
|
+
placeholder="transparent"
|
|
509
|
+
className={`${INPUT_CLASS} flex-1`}
|
|
510
|
+
/>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
</>
|
|
514
|
+
);
|
|
515
|
+
}
|